From 835d21e98e95e0ed2b6b028ddb06fe87427e5488 Mon Sep 17 00:00:00 2001 From: Hisham Muhammad Date: Thu, 5 Apr 2018 15:50:45 -0300 Subject: Add support for namespaces. For details of the new feature, see https://github.com/luarocks/luarocks/wiki/Namespaces This ended up being a huge commit because of some major refactoring motivated by the new feature: * new modules for some object types: * `luarocks.queries` - all functions that look for rocks in local or remote repositories now use objects constructed by this module: query objects contain the name, namespace and query constraints. Dependencies in a rockspec are also stored as query objects. * `luarocks.results` - all individual results produces from queries are returned in this format: result objects contain the name, namespace, version, arch and repo. * the `results` object was renamed to `result_tree`, to better reflect that it is not an array of `result` objects. * `luarocks.vers` was removed, its functionality was moved to better locations. Specifically on namespaces: * Commands that take a rock `name` can now take `namespace/name` (and alternately `--flags=namespace` so that URLs can be also installed with a nominal namespace). * Rocks installed from a namespace now create a `rock_namespace` file alongside `rock_manifest`, which is used when matching namespaced dependencies against locally-installed rocks. * Using namespaced dependencies in a rockspec, requires `rockspec_format = "3.0"`. * Tests under the `#namespaces` hashtag, all using a local repository. --- src/luarocks/admin/index.lua | 2 +- src/luarocks/build.lua | 7 +- src/luarocks/cmd/build.lua | 50 +++++--- src/luarocks/cmd/doc.lua | 9 +- src/luarocks/cmd/download.lua | 3 + src/luarocks/cmd/install.lua | 56 +++++++-- src/luarocks/cmd/list.lua | 10 +- src/luarocks/cmd/make.lua | 2 +- src/luarocks/cmd/pack.lua | 3 +- src/luarocks/cmd/purge.lua | 4 +- src/luarocks/cmd/remove.lua | 4 +- src/luarocks/cmd/search.lua | 21 ++-- src/luarocks/cmd/show.lua | 20 ++-- src/luarocks/cmd/unpack.lua | 8 +- src/luarocks/cmd/write_rockspec.lua | 11 +- src/luarocks/core/util.lua | 6 + src/luarocks/deps.lua | 16 +-- src/luarocks/fetch.lua | 22 +++- src/luarocks/fetch/git.lua | 4 +- src/luarocks/manif.lua | 21 +++- src/luarocks/manif/writer.lua | 29 ++++- src/luarocks/pack.lua | 8 +- src/luarocks/path.lua | 13 ++ src/luarocks/queries.lua | 174 +++++++++++++++++++++------ src/luarocks/remove.lua | 4 +- src/luarocks/repos.lua | 2 +- src/luarocks/results.lua | 62 ++++++++++ src/luarocks/search.lua | 230 +++++++++++++++++------------------- src/luarocks/type/rockspec.lua | 18 ++- src/luarocks/type_check.lua | 8 +- src/luarocks/util.lua | 46 ++++++-- src/luarocks/vers.lua | 126 -------------------- 32 files changed, 612 insertions(+), 387 deletions(-) create mode 100644 src/luarocks/results.lua delete mode 100644 src/luarocks/vers.lua (limited to 'src') diff --git a/src/luarocks/admin/index.lua b/src/luarocks/admin/index.lua index 80371151..76795104 100644 --- a/src/luarocks/admin/index.lua +++ b/src/luarocks/admin/index.lua @@ -4,7 +4,7 @@ local index = {} local util = require("luarocks.util") local fs = require("luarocks.fs") -local vers = require("luarocks.vers") +local vers = require("luarocks.core.vers") local persist = require("luarocks.persist") local dir = require("luarocks.dir") local manif = require("luarocks.manif") diff --git a/src/luarocks/build.lua b/src/luarocks/build.lua index e2419e55..c08fa6c6 100644 --- a/src/luarocks/build.lua +++ b/src/luarocks/build.lua @@ -156,11 +156,13 @@ end -- "all" for all trees, "order" for all trees with priority >= the current default, -- "none" for no trees. -- @param build_only_deps boolean: true to build the listed dependencies only. +-- @param namespace string?: a namespace for the rockspec -- @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 build.build_rockspec(rockspec_file, need_to_fetch, minimal_mode, deps_mode, build_only_deps) +function build.build_rockspec(rockspec_file, need_to_fetch, minimal_mode, deps_mode, build_only_deps, namespace) assert(type(rockspec_file) == "string") assert(type(need_to_fetch) == "boolean") + assert(type(namespace) == "string" or not namespace) local rockspec, err, errcode = fetch.load_rockspec(rockspec_file) if err then @@ -319,6 +321,9 @@ function build.build_rockspec(rockspec_file, need_to_fetch, minimal_mode, deps_m ok, err = writer.make_rock_manifest(name, version) if err then return nil, err end + ok, err = writer.make_namespace_file(name, version, namespace) + if err then return nil, err end + ok, err = repos.deploy_files(name, version, repos.should_wrap_bin_scripts(rockspec), deps_mode) if err then return nil, err end diff --git a/src/luarocks/cmd/build.lua b/src/luarocks/cmd/build.lua index 0d969ff6..1b5c8fdc 100644 --- a/src/luarocks/cmd/build.lua +++ b/src/luarocks/cmd/build.lua @@ -14,6 +14,7 @@ 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") cmd_build.help_summary = "build/compile a rock." cmd_build.help_arguments = "[--pack-binary-rock] [--keep] {|| []}" @@ -47,9 +48,10 @@ or the name of a rock to be fetched from a repository. -- "one" for the current default tree, "all" for all trees, -- "order" for all trees with priority >= the current default, "none" for no trees. -- @param build_only_deps boolean: true to build the listed dependencies only. +-- @param namespace string?: an optional namespace -- @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_file, need_to_fetch, deps_mode, build_only_deps) +local function build_rock(rock_file, need_to_fetch, deps_mode, build_only_deps, namespace) assert(type(rock_file) == "string") assert(type(need_to_fetch) == "boolean") @@ -62,25 +64,29 @@ local function build_rock(rock_file, need_to_fetch, deps_mode, build_only_deps) local rockspec_file = path.rockspec_name_from_rock(rock_file) ok, err = fs.change_dir(unpack_dir) if not ok then return nil, err end - ok, err, errcode = build.build_rockspec(rockspec_file, need_to_fetch, false, deps_mode, build_only_deps) + ok, err, errcode = build.build_rockspec(rockspec_file, need_to_fetch, false, deps_mode, build_only_deps, namespace) fs.pop_dir() return ok, err, errcode end - -local function do_build(name, version, deps_mode, build_only_deps) - if name:match("%.rockspec$") then - return build.build_rockspec(name, true, false, deps_mode, build_only_deps) - elseif name:match("%.src%.rock$") then - return build_rock(name, false, deps_mode, build_only_deps) - elseif name:match("%.all%.rock$") then - return build_rock(name, true, deps_mode, build_only_deps) - elseif name:match("%.rock$") then - return build_rock(name, true, deps_mode, build_only_deps) - elseif not name:match("/") then - local search = require("luarocks.search") - return search.act_on_src_or_rockspec(do_build, name:lower(), version, nil, deps_mode, build_only_deps) + +local function build_file(filename, namespace, deps_mode, build_only_deps) + if filename:match("%.rockspec$") then + return build.build_rockspec(filename, true, false, deps_mode, build_only_deps, namespace) + elseif filename:match("%.src%.rock$") then + return build_rock(filename, false, deps_mode, build_only_deps, namespace) + elseif filename:match("%.all%.rock$") then + return build_rock(filename, true, deps_mode, build_only_deps, namespace) + elseif filename:match("%.rock$") then + return build_rock(filename, true, deps_mode, build_only_deps, namespace) + end +end + +local function do_build(name, version, namespace, deps_mode, build_only_deps) + if name:match("%.rockspec$") or name:match("%.rock$") then + return build_file(name, namespace, deps_mode, build_only_deps) + else + return search.act_on_src_or_rockspec(build_file, name, version, deps_mode, build_only_deps) end - return nil, "Don't know what to do with "..name end --- Driver function for "build" command. @@ -97,16 +103,22 @@ function cmd_build.command(flags, name, version) end assert(type(version) == "string" or not version) + name = util.adjust_name_and_namespace(name, flags) + local deps_mode = deps.get_deps_mode(flags) + local namespace = flags["namespace"] + local build_only_deps = flags["only-deps"] + if flags["pack-binary-rock"] then - return pack.pack_binary_rock(name, version, do_build, name, version, deps.get_deps_mode(flags)) + return pack.pack_binary_rock(name, version, function() return do_build(name, version, namespace, deps_mode) end) else local ok, err = fs.check_command_permissions(flags) if not ok then return nil, err, cfg.errorcodes.PERMISSIONDENIED end - ok, err = do_build(name, version, deps.get_deps_mode(flags), flags["only-deps"]) + + ok, err = do_build(name, version, namespace, deps_mode, build_only_deps) if not ok then return nil, err end name, version = ok, err - if (not flags["only-deps"]) and (not flags["keep"]) and not cfg.keep_other_versions then + if (not build_only_deps) and (not flags["keep"]) and not cfg.keep_other_versions then local ok, err = remove.remove_other_versions(name, version, flags["force"], flags["force-fast"]) if not ok then util.printerr(err) end end diff --git a/src/luarocks/cmd/doc.lua b/src/luarocks/cmd/doc.lua index 5d521276..a2472be4 100644 --- a/src/luarocks/cmd/doc.lua +++ b/src/luarocks/cmd/doc.lua @@ -4,6 +4,7 @@ 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") @@ -61,9 +62,9 @@ function doc.command(flags, name, version) return nil, "Argument missing. "..util.see_help("doc") end - name = name:lower() - - local iname, iversion, repo = search.pick_installed_rock(name, version, flags["tree"]) + name = util.adjust_name_and_namespace(name, flags) + local query = queries.new(name, version) + local iname, iversion, repo = search.pick_installed_rock(query, flags["tree"]) if not iname then util.printout(name..(version and " "..version or "").." is not installed. Looking for it in the rocks servers...") return try_to_open_homepage(name, version) @@ -78,7 +79,7 @@ function doc.command(flags, name, version) return show_homepage(descript.homepage, name, version) end - local directory = path.install_dir(name,version,repo) + local directory = path.install_dir(name, version, repo) local docdir local directories = { "doc", "docs" } diff --git a/src/luarocks/cmd/download.lua b/src/luarocks/cmd/download.lua index 9c119f6e..50c10c0c 100644 --- a/src/luarocks/cmd/download.lua +++ b/src/luarocks/cmd/download.lua @@ -26,6 +26,9 @@ function cmd_download.command(flags, name, version) if type(name) ~= "string" and not flags["all"] then return nil, "Argument missing. "..util.see_help("download") end + + name = util.adjust_name_and_namespace(name, flags) + if not name then name, version = "", "" end local arch diff --git a/src/luarocks/cmd/install.lua b/src/luarocks/cmd/install.lua index 6901a680..80fc4d27 100644 --- a/src/luarocks/cmd/install.lua +++ b/src/luarocks/cmd/install.lua @@ -12,6 +12,7 @@ local writer = require("luarocks.manif.writer") local remove = require("luarocks.remove") local search = require("luarocks.search") local queries = require("luarocks.queries") +local vers = require("luarocks.core.vers") local cfg = require("luarocks.core.cfg") install.help_summary = "Install a rock." @@ -36,10 +37,13 @@ or a filename of a locally available rock. -- @param deps_mode: string: Which trees to check dependencies for: -- "one" for the current default tree, "all" for all trees, -- "order" for all trees with priority >= the current default, "none" for no trees. +-- @param namespace: string?: an optional namespace. -- @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, deps_mode) +function install.install_binary_rock(rock_file, deps_mode, namespace) assert(type(rock_file) == "string") + assert(type(deps_mode) == "string") + assert(type(namespace) == "string" or namespace == nil) local name, version, arch = path.parse_name(rock_file) if not name then @@ -79,6 +83,11 @@ function install.install_binary_rock(rock_file, deps_mode) 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 ok, err, errcode = deps.fulfill_dependencies(rockspec, deps_mode) if err then return nil, err, errcode end @@ -137,6 +146,34 @@ function install.install_binary_rock_deps(rock_file, deps_mode) return name, version end +local function install_rock_file_deps(filename, deps_mode) + local name, version = install.install_binary_rock_deps(filename, deps_mode) + if not name then return nil, version end + + writer.check_dependencies(nil, deps_mode) + return name, version +end + +local function install_rock_file(filename, namespace, deps_mode, keep, force, force_fast) + assert(type(filename) == "string") + assert(type(namespace) == "string" or namespace == nil) + assert(type(deps_mode) == "string") + assert(type(keep) == "boolean" or keep == nil) + assert(type(force) == "boolean" or force == nil) + assert(type(force_fast) == "boolean" or force_fast == nil) + + local name, version = install.install_binary_rock(filename, deps_mode, namespace) + if not name then return nil, version end + + if (not keep) and not cfg.keep_other_versions then + local ok, err = remove.remove_other_versions(name, version, force, force_fast) + if not ok then util.printerr(err) end + end + + writer.check_dependencies(nil, deps_mode) + return name, version +end + --- Driver function for the "install" command. -- @param name string: name of a binary rock. If an URL or pathname -- to a binary rock is given, fetches and installs it. If a rockspec or a @@ -152,6 +189,8 @@ function install.command(flags, name, version) return nil, "Argument missing. "..util.see_help("install") end + name = util.adjust_name_and_namespace(name, flags) + local ok, err = fs.check_command_permissions(flags) if not ok then return nil, err, cfg.errorcodes.PERMISSIONDENIED end @@ -159,21 +198,12 @@ function install.command(flags, name, version) local build = require("luarocks.cmd.build") return build.command(flags, name) elseif name:match("%.rock$") then + local deps_mode = deps.get_deps_mode(flags) if flags["only-deps"] then - ok, err = install.install_binary_rock_deps(name, deps.get_deps_mode(flags)) + return install_rock_file_deps(name, deps_mode) else - ok, err = install.install_binary_rock(name, deps.get_deps_mode(flags)) - end - if not ok then return nil, err end - name, version = ok, err - - if (not flags["only-deps"]) and (not flags["keep"]) and not cfg.keep_other_versions then - local ok, err = remove.remove_other_versions(name, version, flags["force"], flags["force-fast"]) - if not ok then util.printerr(err) end + return install_rock_file(name, flags["namespace"], deps_mode, flags["keep"], flags["force"], flags["force-fast"]) end - - writer.check_dependencies(nil, deps.get_deps_mode(flags)) - return name, version else local url, err = search.find_suitable_rock(queries.new(name:lower(), version)) if not url then diff --git a/src/luarocks/cmd/list.lua b/src/luarocks/cmd/list.lua index e07c4ff3..102a08f0 100644 --- a/src/luarocks/cmd/list.lua +++ b/src/luarocks/cmd/list.lua @@ -5,7 +5,7 @@ local list = {} local search = require("luarocks.search") local queries = require("luarocks.queries") -local vers = require("luarocks.vers") +local vers = require("luarocks.core.vers") local cfg = require("luarocks.core.cfg") local util = require("luarocks.util") local path = require("luarocks.path") @@ -24,7 +24,7 @@ list.help = [[ local function check_outdated(trees, query) local results_installed = {} for _, tree in ipairs(trees) do - search.manifest_search(results_installed, path.rocks_dir(tree), query) + search.local_manifest_search(results_installed, path.rocks_dir(tree), query) end local outdated = {} for name, versions in util.sortedpairs(results_installed) do @@ -32,7 +32,7 @@ local function check_outdated(trees, query) table.sort(versions, vers.compare_versions) local latest_installed = versions[1] - local query_available = queries.new(name:lower(), nil, false) + local query_available = queries.new(name:lower()) local results_available, err = search.search_repos(query_available) if results_available[name] then @@ -81,13 +81,13 @@ function list.command(flags, filter, version) local results = {} for _, tree in ipairs(trees) do - local ok, err, errcode = search.manifest_search(results, path.rocks_dir(tree), query) + 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("Installed rocks for Lua "..cfg.lua_version..":", flags["porcelain"]) - search.print_results(results, flags["porcelain"]) + search.print_result_tree(results, flags["porcelain"]) return true end diff --git a/src/luarocks/cmd/make.lua b/src/luarocks/cmd/make.lua index 8f01856e..850da6f4 100644 --- a/src/luarocks/cmd/make.lua +++ b/src/luarocks/cmd/make.lua @@ -72,7 +72,7 @@ function make.command(flags, rockspec) if not rspec then return nil, err end - return pack.pack_binary_rock(rspec.name, rspec.version, build.build_rockspec, rockspec, false, true, deps.get_deps_mode(flags)) + return pack.pack_binary_rock(rspec.name, rspec.version, function() return build.build_rockspec(rockspec, false, true, deps.get_deps_mode(flags)) end) else local ok, err = fs.check_command_permissions(flags) if not ok then return nil, err, cfg.errorcodes.PERMISSIONDENIED end diff --git a/src/luarocks/cmd/pack.lua b/src/luarocks/cmd/pack.lua index e43e5b3f..3d3cdef5 100644 --- a/src/luarocks/cmd/pack.lua +++ b/src/luarocks/cmd/pack.lua @@ -32,7 +32,8 @@ function cmd_pack.command(flags, arg, version) if arg:match(".*%.rockspec") then file, err = pack.pack_source_rock(arg) else - file, err = pack.pack_installed_rock(arg:lower(), version, flags["tree"]) + local name = util.adjust_name_and_namespace(arg, flags) + file, err = pack.pack_installed_rock(name, version, flags["tree"]) end if err then return nil, err diff --git a/src/luarocks/cmd/purge.lua b/src/luarocks/cmd/purge.lua index 15efb0ef..5f868e60 100644 --- a/src/luarocks/cmd/purge.lua +++ b/src/luarocks/cmd/purge.lua @@ -7,7 +7,7 @@ local util = require("luarocks.util") local fs = require("luarocks.fs") local path = require("luarocks.path") local search = require("luarocks.search") -local vers = require("luarocks.vers") +local vers = require("luarocks.core.vers") local repos = require("luarocks.repos") local writer = require("luarocks.manif.writer") local cfg = require("luarocks.core.cfg") @@ -45,7 +45,7 @@ function purge.command(flags) local ok, err = fs.check_command_permissions(flags) if not ok then return nil, err, cfg.errorcodes.PERMISSIONDENIED end - search.manifest_search(results, path.rocks_dir(tree), queries.all()) + search.local_manifest_search(results, path.rocks_dir(tree), queries.all()) local sort = function(a,b) return vers.compare_versions(b,a) end if flags["old-versions"] then diff --git a/src/luarocks/cmd/remove.lua b/src/luarocks/cmd/remove.lua index 2624854d..4af036aa 100644 --- a/src/luarocks/cmd/remove.lua +++ b/src/luarocks/cmd/remove.lua @@ -36,6 +36,8 @@ function cmd_remove.command(flags, name, version) if type(name) ~= "string" then return nil, "Argument missing. "..util.see_help("remove") end + + name = util.adjust_name_and_namespace(name, flags) local deps_mode = flags["deps-mode"] or cfg.deps_mode @@ -51,7 +53,7 @@ function cmd_remove.command(flags, name, version) local results = {} name = name:lower() - search.manifest_search(results, cfg.rocks_dir, queries.new(name, version)) + search.local_manifest_search(results, cfg.rocks_dir, queries.new(name, version)) if not results[name] then return nil, "Could not find rock '"..name..(version and " "..version or "").."' in "..path.rocks_tree_to_string(cfg.root_dir) end diff --git a/src/luarocks/cmd/search.lua b/src/luarocks/cmd/search.lua index ccdbda7b..8f4d014e 100644 --- a/src/luarocks/cmd/search.lua +++ b/src/luarocks/cmd/search.lua @@ -7,6 +7,7 @@ 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") cmd_search.help_summary = "Query the LuaRocks servers." cmd_search.help_arguments = "[--source] [--binary] { [] | --all }" @@ -23,19 +24,20 @@ cmd_search.help = [[ --- 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 results table: A search results table. +-- @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(results) +local function split_source_and_binary_results(result_tree) local sources, binaries = {}, {} - for name, versions in pairs(results) do + 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 - search.store_result(where, name, version, repo.arch, repo.repo) + local entry = results.new(name, version, repo.repo, repo.arch) + search.store_result(where, entry) end end end @@ -48,6 +50,9 @@ end -- @return boolean or (nil, string): True if build was successful; nil and an -- error message otherwise. function cmd_search.command(flags, name, version) + + name = util.adjust_name_and_namespace(name, flags) + if flags["all"] then name, version = "", nil end @@ -57,18 +62,18 @@ function cmd_search.command(flags, name, version) end local query = queries.new(name:lower(), version, true) - local results, err = search.search_repos(query) + local result_tree, err = search.search_repos(query) local porcelain = flags["porcelain"] local full_name = name .. (version and " " .. version or "") util.title(full_name .. " - Search results for Lua "..cfg.lua_version..":", porcelain, "=") - local sources, binaries = split_source_and_binary_results(results) + local sources, binaries = split_source_and_binary_results(result_tree) if next(sources) and not flags["binary"] then util.title("Rockspecs and source rocks:", porcelain) - search.print_results(sources, porcelain) + search.print_result_tree(sources, porcelain) end if next(binaries) and not flags["source"] then util.title("Binary and pure-Lua rocks:", porcelain) - search.print_results(binaries, porcelain) + search.print_result_tree(binaries, porcelain) end return true end diff --git a/src/luarocks/cmd/show.lua b/src/luarocks/cmd/show.lua index 21c75cf2..9f8bc472 100644 --- a/src/luarocks/cmd/show.lua +++ b/src/luarocks/cmd/show.lua @@ -2,11 +2,12 @@ -- Shows information about an installed rock. local show = {} +local queries = require("luarocks.queries") local search = require("luarocks.search") local cfg = require("luarocks.core.cfg") local util = require("luarocks.util") local path = require("luarocks.path") -local vers = require("luarocks.vers") +local vers = require("luarocks.core.vers") local fetch = require("luarocks.fetch") local manif = require("luarocks.manif") local repos = require("luarocks.repos") @@ -57,12 +58,12 @@ local function format_text(text) return (table.concat(paragraphs, "\n\n"):gsub("%s$", "")) end -local function installed_rock_label(name, tree) +local function installed_rock_label(dep, tree) local installed, version - if cfg.rocks_provided[name] then - installed, version = true, cfg.rocks_provided[name] + if cfg.rocks_provided[dep.name] then + installed, version = true, cfg.rocks_provided[dep.name] else - installed, version = search.pick_installed_rock(name, nil, tree) + installed, version = search.pick_installed_rock(dep, tree) end return installed and "(using "..version..")" or "(missing)" end @@ -81,14 +82,17 @@ function show.command(flags, name, version) if not name then return nil, "Argument missing. "..util.see_help("show") end + + name = util.adjust_name_and_namespace(name, flags) + local query = queries.new(name, version) local repo, repo_url - name, version, repo, repo_url = search.pick_installed_rock(name:lower(), version, flags["tree"]) + name, version, repo, repo_url = search.pick_installed_rock(query, flags["tree"]) if not name then return nil, version end - local directory = path.install_dir(name,version,repo) + local directory = path.install_dir(name, version, repo) 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 @@ -147,7 +151,7 @@ function show.command(flags, name, version) util.printout("Depends on:") for _, dep in ipairs(rockspec.dependencies) do direct_deps[dep.name] = true - util.printout("\t"..vers.show_dep(dep).." "..installed_rock_label(dep.name, flags["tree"])) + util.printout("\t"..tostring(dep).." "..installed_rock_label(dep, flags["tree"])) end end local has_indirect_deps diff --git a/src/luarocks/cmd/unpack.lua b/src/luarocks/cmd/unpack.lua index c50701b0..f4232266 100644 --- a/src/luarocks/cmd/unpack.lua +++ b/src/luarocks/cmd/unpack.lua @@ -92,7 +92,7 @@ end -- @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) +local function run_unpacker(file, namespace, force) assert(type(file) == "string") local base_name = dir.base_name(file) @@ -153,11 +153,13 @@ function unpack.command(flags, name, version) return nil, "Argument missing. "..util.see_help("unpack") end + name = util.adjust_name_and_namespace(name, flags) + if name:match(".*%.rock") or name:match(".*%.rockspec") then - return run_unpacker(name, flags["force"]) + return run_unpacker(name, flags["namespace"], flags["force"]) else local search = require("luarocks.search") - return search.act_on_src_or_rockspec(run_unpacker, name:lower(), version) + return search.act_on_src_or_rockspec(run_unpacker, name, version) end end diff --git a/src/luarocks/cmd/write_rockspec.lua b/src/luarocks/cmd/write_rockspec.lua index dc6eaab1..88223857 100644 --- a/src/luarocks/cmd/write_rockspec.lua +++ b/src/luarocks/cmd/write_rockspec.lua @@ -9,7 +9,7 @@ local path = require("luarocks.path") local persist = require("luarocks.persist") local type_rockspec = require("luarocks.type.rockspec") local util = require("luarocks.util") -local vers = require("luarocks.vers") +local vers = require("luarocks.core.vers") write_rockspec.help_summary = "Write a template for a rockspec file." write_rockspec.help_arguments = "[--output= ...] [] [] [|]" @@ -227,6 +227,9 @@ local function rockspec_cleanup(rockspec) end function write_rockspec.command(flags, name, version, url_or_dir) + + name = util.adjust_name_and_namespace(name, flags) + if not name then url_or_dir = "." elseif not version then @@ -286,7 +289,11 @@ function write_rockspec.command(flags, name, version, url_or_dir) } path.configure_paths(rockspec) rockspec.source.protocol = protocol - rockspec.format_is_at_least = vers.format_is_at_least + + local parsed_format = vers.parse_version(rockspec.rockspec_format or "1.0") + rockspec.format_is_at_least = function(_, v) + return parsed_format >= vers.parse_version(v) + end configure_lua_version(rockspec, flags["lua-version"]) diff --git a/src/luarocks/core/util.lua b/src/luarocks/core/util.lua index 1a963a65..99fd1618 100644 --- a/src/luarocks/core/util.lua +++ b/src/luarocks/core/util.lua @@ -204,6 +204,12 @@ function util.printerr(...) io.stderr:write("\n") end +--- Display a warning message. +-- @param msg string: the warning message +function util.warning(msg) + util.printerr("Warning: "..msg) +end + --- Simple sort function used as a default for util.sortedpairs. local function default_sort(a, b) local ta = type(a) diff --git a/src/luarocks/deps.lua b/src/luarocks/deps.lua index 229c3843..182bbfb2 100644 --- a/src/luarocks/deps.lua +++ b/src/luarocks/deps.lua @@ -7,7 +7,7 @@ local manif = require("luarocks.manif") local path = require("luarocks.path") local dir = require("luarocks.dir") local util = require("luarocks.util") -local vers = require("luarocks.vers") +local vers = require("luarocks.core.vers") local queries = require("luarocks.queries") --- Attempt to match a dependency to an installed rock. @@ -28,7 +28,7 @@ local function match_dep(dep, blacklist, deps_mode, rocks_provided) -- Provided rocks have higher priority than manifest's rocks. versions = { provided } else - versions = manif.get_versions(dep.name, deps_mode) + versions = manif.get_versions(dep, deps_mode) end local latest_version @@ -115,7 +115,7 @@ function deps.report_missing_dependencies(name, version, dependencies, deps_mode first_missing_dep = false end - util.printout((" %s (%s)"):format(vers.show_dep(dep), rock_status(dep.name, deps_mode, rocks_provided))) + util.printout((" %s (%s)"):format(tostring(dep), rock_status(dep.name, deps_mode, rocks_provided))) end end end @@ -172,11 +172,11 @@ function deps.fulfill_dependencies(rockspec, deps_mode) end util.printout(("%s %s depends on %s (%s)"):format( - rockspec.name, rockspec.version, vers.show_dep(dep), rock_status(dep.name, deps_mode, rockspec.rocks_provided))) + rockspec.name, rockspec.version, tostring(dep), rock_status(dep.name, deps_mode, rockspec.rocks_provided))) if dep.constraints[1] and dep.constraints[1].no_upgrade then util.printerr("This version of "..rockspec.name.." is designed for use with") - util.printerr(vers.show_dep(dep)..", but is configured to avoid upgrading it") + util.printerr(tostring(dep)..", but is configured to avoid upgrading it") util.printerr("automatically. Please upgrade "..dep.name.." with") util.printerr(" luarocks install "..dep.name) util.printerr("or choose an older version of "..rockspec.name.." with") @@ -184,12 +184,12 @@ function deps.fulfill_dependencies(rockspec, deps_mode) return nil, "Failed matching dependencies" end - local url, search_err = search.find_suitable_rock(queries.from_constraints(dep.name, dep.constraints)) + local url, search_err = search.find_suitable_rock(dep) if not url then - return nil, "Could not satisfy dependency "..vers.show_dep(dep)..": "..search_err + return nil, "Could not satisfy dependency "..tostring(dep)..": "..search_err end util.printout("Installing "..url) - local ok, install_err, errcode = install.command({deps_mode = deps_mode}, url) + local ok, install_err, errcode = install.command({deps_mode = deps_mode, namespace = dep.namespace}, url) if not ok then return nil, "Failed installing dependency: "..url.." - "..install_err, errcode end diff --git a/src/luarocks/fetch.lua b/src/luarocks/fetch.lua index de4a3dee..db99c3d5 100644 --- a/src/luarocks/fetch.lua +++ b/src/luarocks/fetch.lua @@ -6,7 +6,8 @@ local fs = require("luarocks.fs") local dir = require("luarocks.dir") local type_rockspec = require("luarocks.type.rockspec") local path = require("luarocks.path") -local vers = require("luarocks.vers") +local vers = require("luarocks.core.vers") +local queries = require("luarocks.queries") local persist = require("luarocks.persist") local util = require("luarocks.util") local cfg = require("luarocks.core.cfg") @@ -210,8 +211,21 @@ function fetch.load_local_rockspec(filename, quick) return nil, filename..": "..err end end - - rockspec.format_is_at_least = vers.format_is_at_least + + rockspec.format_is_at_least = function(_, v) + return parsed_format >= vers.parse_version(v) + end + + --- Check if rockspec format version satisfies version requirement. + -- @param rockspec table: The rockspec table. + -- @param version string: required version. + -- @return boolean: true if rockspec format matches version or is newer, false otherwise. + do + local parsed_format = vers.parse_version(rockspec.rockspec_format or "1.0") + rockspec.format_is_at_least = function(self, version) + return parsed_format >= vers.parse_version(version) + end + end util.platform_overrides(rockspec.build) util.platform_overrides(rockspec.dependencies) @@ -262,7 +276,7 @@ function fetch.load_local_rockspec(filename, quick) if rockspec.dependencies then for i = 1, #rockspec.dependencies do - local parsed, err = vers.parse_dep(rockspec.dependencies[i]) + local parsed, err = queries.from_dep_string(rockspec.dependencies[i]) if not parsed then return nil, "Parse error processing dependency '"..rockspec.dependencies[i].."': "..tostring(err) end diff --git a/src/luarocks/fetch/git.lua b/src/luarocks/fetch/git.lua index efd62e6d..a14a5bfb 100644 --- a/src/luarocks/fetch/git.lua +++ b/src/luarocks/fetch/git.lua @@ -6,14 +6,14 @@ local unpack = unpack or table.unpack local fs = require("luarocks.fs") local dir = require("luarocks.dir") -local vers = require("luarocks.vers") +local vers = require("luarocks.core.vers") local util = require("luarocks.util") local cached_git_version --- Get git version. -- @param git_cmd string: name of git command. --- @return table: git version as returned by luarocks.vers.parse_version. +-- @return table: git version as returned by luarocks.core.vers.parse_version. local function git_version(git_cmd) if not cached_git_version then local version_line = io.popen(fs.Q(git_cmd)..' --version'):read() diff --git a/src/luarocks/manif.lua b/src/luarocks/manif.lua index df9f22ae..5ac9920d 100644 --- a/src/luarocks/manif.lua +++ b/src/luarocks/manif.lua @@ -230,9 +230,12 @@ end -- or "all", to use all trees. -- @return table: An array of strings listing installed -- versions of a package. -function manif.get_versions(name, deps_mode) - assert(type(name) == "string") +function manif.get_versions(dep, deps_mode) + assert(type(dep) == "table") assert(type(deps_mode) == "string") + + local name = dep.name + local namespace = dep.namespace local version_set = {} path.map_trees(deps_mode, function(tree) @@ -240,7 +243,19 @@ function manif.get_versions(name, deps_mode) if manifest and manifest.repository[name] then for version in pairs(manifest.repository[name]) do - version_set[version] = true + if dep.namespace then + local ns_file = path.rock_namespace_file(name, version, tree) + local fd = io.open(ns_file, "r") + if fd then + local ns = fd:read("*a") + fd:close() + if ns == namespace then + version_set[version] = true + end + end + else + version_set[version] = true + end end end end) diff --git a/src/luarocks/manif/writer.lua b/src/luarocks/manif/writer.lua index e6edd27d..a6c70f4d 100644 --- a/src/luarocks/manif/writer.lua +++ b/src/luarocks/manif/writer.lua @@ -5,7 +5,7 @@ local cfg = require("luarocks.core.cfg") local search = require("luarocks.search") local repos = require("luarocks.repos") local deps = require("luarocks.deps") -local vers = require("luarocks.vers") +local vers = require("luarocks.core.vers") local fs = require("luarocks.fs") local util = require("luarocks.util") local dir = require("luarocks.dir") @@ -277,6 +277,33 @@ function writer.make_rock_manifest(name, version) save_table(install_dir, "rock_manifest", rock_manifest ) end +-- Writes a 'rock_namespace' file in a locally installed rock directory. +-- @param name string: the rock name (may be in user/rock format) +-- @param version string: the rock version +-- @param namespace string?: the namespace +-- @return true if successful (or unnecessary, if there is no namespace), +-- or nil and an error message. +function writer.make_namespace_file(name, version, namespace) + assert(type(name) == "string") + assert(type(version) == "string") + assert(type(namespace) == "string" or not namespace) + name = util.adjust_name_and_namespace(name, { namespace = namespace }) + name, namespace = util.split_namespace(name) + if not namespace then + return true + end + local fd, err = io.open(path.rock_namespace_file(name, version), "w") + if not fd then + return nil, err + end + local ok, err = fd:write(namespace) + if not ok then + return nil, err + end + fd:close() + return true +end + --- Scan a LuaRocks repository and output a manifest file. -- A file called 'manifest' will be written in the root of the given -- repository directory. diff --git a/src/luarocks/pack.lua b/src/luarocks/pack.lua index b721a209..a02fb677 100644 --- a/src/luarocks/pack.lua +++ b/src/luarocks/pack.lua @@ -4,6 +4,7 @@ local pack = {} local unpack = unpack or table.unpack +local queries = require("luarocks.queries") local path = require("luarocks.path") local repos = require("luarocks.repos") local fetch = require("luarocks.fetch") @@ -80,8 +81,9 @@ function pack.pack_installed_rock(name, version, tree) assert(type(name) == "string") assert(type(version) == "string" or not version) + local query = queries.new(name, version) local repo, repo_url - name, version, repo, repo_url = search.pick_installed_rock(name, version, tree) + name, version, repo, repo_url = search.pick_installed_rock(query, tree) if not name then return nil, version end @@ -126,7 +128,7 @@ function pack.pack_installed_rock(name, version, tree) return rock_file end -function pack.pack_binary_rock(name, version, cmd, ...) +function pack.pack_binary_rock(name, version, cmd) -- The --pack-binary-rock option for "luarocks build" basically performs -- "luarocks build" on a temporary tree and then "luarocks pack". The @@ -142,7 +144,7 @@ function pack.pack_binary_rock(name, version, cmd, ...) util.schedule_function(fs.delete, temp_dir) path.use_tree(temp_dir) - local ok, err = cmd(...) + local ok, err = cmd() if not ok then return nil, err end diff --git a/src/luarocks/path.lua b/src/luarocks/path.lua index 8a56233f..7a569169 100644 --- a/src/luarocks/path.lua +++ b/src/luarocks/path.lua @@ -97,6 +97,19 @@ function path.rock_manifest_file(name, version, tree) return dir.path(path.rocks_dir(tree), name, version, "rock_manifest") end +--- Get the local filename of the rock_namespace file of an installed rock. +-- @param name string: The package name (without a namespace). +-- @param version string: The package version. +-- @param tree string or nil: If given, specifies the local tree to use. +-- @return string: The resulting path -- does not guarantee that +-- the package (and by extension, the file) exists. +function path.rock_namespace_file(name, version, tree) + assert(type(name) == "string") + assert(type(version) == "string") + tree = tree or cfg.root_dir + return dir.path(path.rocks_dir(tree), name, version, "rock_namespace") +end + --- Get the local installation directory for C libraries of a package. -- @param name string: The package name. -- @param version string: The package version. diff --git a/src/luarocks/queries.lua b/src/luarocks/queries.lua index 5bd7462e..888d9cf7 100644 --- a/src/luarocks/queries.lua +++ b/src/luarocks/queries.lua @@ -1,10 +1,17 @@ local queries = {} -local vers = require("luarocks.vers") +local vers = require("luarocks.core.vers") +local util = require("luarocks.util") local cfg = require("luarocks.core.cfg") -local safer = require("safer") +local query_mt = {} + +query_mt.__index = query_mt + +function query_mt.type() + return "query" +end --- Convert the arch field of a query table to table format. -- @param input string, table or nil @@ -28,24 +35,13 @@ local function arch_to_table(input) end end --- Split name and namespace of a package name. --- @param name a name that may be in "namespace/name" format --- @return string, string? - name and optionally a namespace -local function split_namespace(name) - local p1, p2 = name:match("^([^/]+)/([^/]+)$") - if p1 then - return p2, p1 - end - return name -end - --- Prepare a query in dependency table format. --- @param name string: The query name. --- @param version string or nil: --- @param substring boolean: match substrings of the name +-- @param name string: the package name, may contain a namespace. +-- @param version string?: the package version. +-- @param substring boolean?: match substrings of the name -- (default is false, match full name) --- @param arch string: a string with pipe-separated accepted arch values --- @param operator string: operator for version matching (default is "==") +-- @param arch string?: a string with pipe-separated accepted arch values +-- @param operator string?: operator for version matching (default is "==") -- @return table: A query in table format function queries.new(name, version, substring, arch, operator) assert(type(name) == "string") @@ -57,9 +53,9 @@ function queries.new(name, version, substring, arch, operator) operator = operator or "==" local namespace - name, namespace = split_namespace(name) + name, namespace = util.split_namespace(name) - local query = { + local self = { name = name, namespace = namespace, constraints = {}, @@ -67,9 +63,9 @@ function queries.new(name, version, substring, arch, operator) arch = arch_to_table(arch), } if version then - table.insert(query.constraints, { op = operator, version = vers.parse_version(version)}) + table.insert(self.constraints, { op = operator, version = vers.parse_version(version)}) end - return safer.readonly(query) + return setmetatable(self, query_mt) end -- Query for all packages @@ -80,17 +76,129 @@ function queries.all(arch) return queries.new("", nil, true, arch) end -function queries.from_constraints(name, constraints) - local namespace - name, namespace = split_namespace(name) - local query = { - name = name, - namespace = namespace, - constraints = constraints, - substring = false, - arch = arch_to_table(nil), - } - return safer.readonly(query) +do + local parse_constraints + do + local parse_constraint + do + local operators = { + ["=="] = "==", + ["~="] = "~=", + [">"] = ">", + ["<"] = "<", + [">="] = ">=", + ["<="] = "<=", + ["~>"] = "~>", + -- plus some convenience translations + [""] = "==", + ["="] = "==", + ["!="] = "~=" + } + + --- Consumes a constraint from a string, converting it to table format. + -- For example, a string ">= 1.0, > 2.0" is converted to a table in the + -- format {op = ">=", version={1,0}} and the rest, "> 2.0", is returned + -- back to the caller. + -- @param input string: A list of constraints in string format. + -- @return (table, string) or nil: A table representing the same + -- constraints and the string with the unused input, or nil if the + -- input string is invalid. + parse_constraint = function(input) + assert(type(input) == "string") + + local no_upgrade, op, version, rest = input:match("^(@?)([<>=~!]*)%s*([%w%.%_%-]+)[%s,]*(.*)") + local _op = operators[op] + version = vers.parse_version(version) + if not _op then + return nil, "Encountered bad constraint operator: '"..tostring(op).."' in '"..input.."'" + end + if not version then + return nil, "Could not parse version from constraint: '"..input.."'" + end + return { op = _op, version = version, no_upgrade = no_upgrade=="@" and true or nil }, rest + end + end + + --- Convert a list of constraints from string to table format. + -- For example, a string ">= 1.0, < 2.0" is converted to a table in the format + -- {{op = ">=", version={1,0}}, {op = "<", version={2,0}}}. + -- Version tables use a metatable allowing later comparison through + -- relational operators. + -- @param input string: A list of constraints in string format. + -- @return table or nil: A table representing the same constraints, + -- or nil if the input string is invalid. + parse_constraints = function(input) + assert(type(input) == "string") + + local constraints, oinput, constraint = {}, input + while #input > 0 do + constraint, input = parse_constraint(input) + if constraint then + table.insert(constraints, constraint) + else + return nil, "Failed to parse constraint '"..tostring(oinput).."' with error: ".. input + end + end + return constraints + end + end + + --- Prepare a query in dependency table format. + -- @param depstr string: A dependency in string format + -- as entered in rockspec files. + -- @return table: A query in table format, or nil and an error message in case of errors. + function queries.from_dep_string(depstr) + assert(type(depstr) == "string") + + local ns_name, rest = depstr:match("^%s*([a-zA-Z0-9%.%-%_]*/?[a-zA-Z0-9][a-zA-Z0-9%.%-%_]*)%s*([^/]*)") + if not ns_name then + return nil, "failed to extract dependency name from '"..depstr.."'" + end + + local constraints, err = parse_constraints(rest) + if not constraints then + return nil, err + end + + local name, namespace = util.split_namespace(ns_name) + + local self = { + name = name, + namespace = namespace, + constraints = constraints, + substring = false, + arch = arch_to_table(nil), + } + return setmetatable(self, query_mt) + end +end + +--- Build a string representation of a query package name. +-- Includes namespace, name and version, but not arch or constraints. +-- @param query table: a query table +-- @return string: a result such as `my_user/my_rock 1.0` or `my_rock`. +function queries:__tostring() + local out = {} + if self.namespace then + table.insert(out, self.namespace) + table.insert(out, "/") + end + table.insert(out, self.name) + + if #self.constraints > 0 then + local pretty = {} + for _, c in ipairs(self.constraints) do + if c.op == "==" then + table.insert(pretty, tostring(c.version)) + else + table.insert(pretty, c.op .. " " .. tostring(c.version)) + end + end + table.insert(out, " ") + table.insert(out, table.concat(pretty, ", ")) + end + + return table.concat(out) end return queries diff --git a/src/luarocks/remove.lua b/src/luarocks/remove.lua index a469d149..1b4e3562 100644 --- a/src/luarocks/remove.lua +++ b/src/luarocks/remove.lua @@ -24,7 +24,7 @@ local function check_dependents(name, versions, deps_mode) end local local_rocks = {} local query_all = queries.all() - search.manifest_search(local_rocks, cfg.rocks_dir, query_all) + search.local_manifest_search(local_rocks, cfg.rocks_dir, query_all) local_rocks[name] = nil for rock_name, rock_versions in pairs(local_rocks) do for rock_version, _ in pairs(rock_versions) do @@ -106,7 +106,7 @@ end function remove.remove_other_versions(name, version, force, fast) local results = {} local query = queries.new(name, version, false, nil, "~=") - search.manifest_search(results, cfg.rocks_dir, query) + search.local_manifest_search(results, cfg.rocks_dir, query) if results[name] then return remove.remove_search_results(results, name, cfg.deps_mode, force, fast) end diff --git a/src/luarocks/repos.lua b/src/luarocks/repos.lua index 12927153..35f5e3bc 100644 --- a/src/luarocks/repos.lua +++ b/src/luarocks/repos.lua @@ -8,7 +8,7 @@ local cfg = require("luarocks.core.cfg") local util = require("luarocks.util") local dir = require("luarocks.dir") local manif = require("luarocks.manif") -local vers = require("luarocks.vers") +local vers = require("luarocks.core.vers") -- Tree of files installed by a package are stored -- in its rock manifest. Some of these files have to diff --git a/src/luarocks/results.lua b/src/luarocks/results.lua new file mode 100644 index 00000000..3e743883 --- /dev/null +++ b/src/luarocks/results.lua @@ -0,0 +1,62 @@ +local results = {} + +local vers = require("luarocks.core.vers") +local util = require("luarocks.util") + +local result_mt = {} + +result_mt.__index = result_mt + +function result_mt.type() + return "result" +end + +function results.new(name, version, repo, arch, namespace) + assert(type(name) == "string") + assert(type(version) == "string") + assert(type(repo) == "string") + assert(type(arch) == "string" or not arch) + assert(type(namespace) == "string" or not namespace) + + if not namespace then + name, namespace = util.split_namespace(name) + end + + local self = { + name = name, + version = version, + namespace = namespace, + arch = arch, + repo = repo, + } + + return setmetatable(self, result_mt) +end + +--- Test the name field of a query. +-- If query has a boolean field substring set to true, +-- then substring match is performed; otherwise, exact string +-- comparison is done. +-- @param query table: A query in dependency table format. +-- @param name string: A package name. +-- @return boolean: True if names match, false otherwise. +local function match_name(query, name) + if query.substring then + return name:find(query.name, 0, true) and true or false + else + return name == query.name + end +end + +--- Returns true if the result satisfies a given query. +-- @param query: a query. +-- @return boolean. +function result_mt:satisfies(query) + assert(query:type() == "query") + return match_name(query, self.name) + and (query.arch[self.arch] or query.arch["any"]) + and ((not query.namespace) or (query.namespace == self.namespace)) + and vers.match_constraints(vers.parse_version(self.version), query.constraints) +end + +return results diff --git a/src/luarocks/search.lua b/src/luarocks/search.lua index 4e0cb847..54ddd517 100644 --- a/src/luarocks/search.lua +++ b/src/luarocks/search.lua @@ -3,103 +3,68 @@ local search = {} local dir = require("luarocks.dir") local path = require("luarocks.path") local manif = require("luarocks.manif") -local vers = require("luarocks.vers") +local vers = require("luarocks.core.vers") local cfg = require("luarocks.core.cfg") local util = require("luarocks.util") local queries = require("luarocks.queries") +local results = require("luarocks.results") ---- Store a search result (a rock or rockspec) in the results table. --- @param results table: The results table, where keys are package names and +--- Store a search result (a rock or rockspec) in the result tree. +-- @param result_tree table: The result tree, where keys are package names and -- values are tables matching version strings to arrays of -- tables with fields "arch" and "repo". --- @param name string: Package name. --- @param version string: Package version. --- @param arch string: Architecture of rock ("all", "src" or platform --- identifier), "rockspec" or "installed" --- @param repo string: Pathname of a local repository of URL of --- rocks server. -function search.store_result(results, name, version, arch, repo) - assert(type(results) == "table") - assert(type(name) == "string") - assert(type(version) == "string") - assert(type(arch) == "string") - assert(type(repo) == "string") - - if not results[name] then results[name] = {} end - if not results[name][version] then results[name][version] = {} end - table.insert(results[name][version], { - arch = arch, - repo = repo - }) -end +-- @param result table: A result. +function search.store_result(result_tree, result) + assert(type(result_tree) == "table") + assert(result:type() == "result") ---- Test the name field of a query. --- If query has a boolean field substring set to true, --- then substring match is performed; otherwise, exact string --- comparison is done. --- @param query table: A query in dependency table format. --- @param name string: A package name. --- @return boolean: True if names match, false otherwise. -local function match_name(query, name) - assert(type(query) == "table") - assert(type(name) == "string") - if query.substring then - return name:find(query.name, 0, true) and true or false - else - return name == query.name - end + local name = result.name + local version = result.version + + if not result_tree[name] then result_tree[name] = {} end + if not result_tree[name][version] then result_tree[name][version] = {} end + table.insert(result_tree[name][version], { + arch = result.arch, + repo = result.repo + }) end ---- Store a match in a results table if version matches query. +--- Store a match in a result tree if version matches query. -- Name, version, arch and repository path are stored in a given -- table, optionally checking if version and arch (if given) match -- a query. --- @param results table: The results table, where keys are package names and +-- @param result_tree table: The result tree, where keys are package names and -- values are tables matching version strings to arrays of -- tables with fields "arch" and "repo". --- @param repo string: URL or pathname of the repository. --- @param name string: The name of the package being tested. --- @param version string: The version of the package being tested. --- @param arch string: The arch of the package being tested. --- @param query table: A table describing the query in dependency --- format (for example, {name = "filesystem", substring = true, --- constraints = {op = "~>", version = {1,0}}}, arch = "rockspec"). --- If the arch field is omitted, the local architecture (cfg.arch) --- is used. The special value "any" is also recognized, returning all --- matches regardless of architecture. -local function store_if_match(results, repo, name, version, arch, query) - if match_name(query, name) then - if query.arch[arch] or query.arch["any"] then - if vers.match_constraints(vers.parse_version(version), query.constraints) then - search.store_result(results, name, version, arch, repo) - end - end +-- @param result table: a result object. +-- @param query table: a query object. +local function store_if_match(result_tree, result, query) + assert(result:type() == "result") + assert(query:type() == "query") + + if result:satisfies(query) then + search.store_result(result_tree, result) end end --- Perform search on a local repository. -- @param repo string: The pathname of the local repository. --- @param query table: A table describing the query in dependency --- format (for example, {name = "filesystem", substring = true, --- constraints = {op = "~>", version = {1,0}}}, arch = "rockspec"). --- If the arch field is omitted, the local architecture (cfg.arch) --- is used. The special value "any" is also recognized, returning all --- matches regardless of architecture. --- @param results table or nil: If given, this table will store the --- results; if not given, a new table will be created. --- @return table: The results table, where keys are package names and +-- @param query table: a query object. +-- @param result_tree table or nil: If given, this table will store the +-- result tree; if not given, a new table will be created. +-- @return table: The result tree, where keys are package names and -- values are tables matching version strings to arrays of -- tables with fields "arch" and "repo". --- If a table was given in the "results" parameter, that is the result value. -function search.disk_search(repo, query, results) +-- If a table was given in the "result_tree" parameter, that is the result value. +function search.disk_search(repo, query, result_tree) assert(type(repo) == "string") - assert(type(query) == "table") - assert(type(results) == "table" or not results) + assert(query:type() == "query") + assert(type(result_tree) == "table" or not result_tree) local fs = require("luarocks.fs") - if not results then - results = {} + if not result_tree then + result_tree = {} end for name in fs.dir(repo) do @@ -107,38 +72,49 @@ function search.disk_search(repo, query, results) local rname, rversion, rarch = path.parse_name(name) if rname and (pathname:match(".rockspec$") or pathname:match(".rock$")) then - store_if_match(results, repo, rname, rversion, rarch, query) + local result = results.new(rname, rversion, repo, rarch) + store_if_match(result_tree, result, query) elseif fs.is_dir(pathname) then for version in fs.dir(pathname) do if version:match("-%d+$") then - store_if_match(results, repo, name, version, "installed", query) + local namespace = nil + -- FIXME read rock_namespace file + local result = results.new(name, version, repo, "installed", namespace) + store_if_match(result_tree, result, query) end end end end - return results + return result_tree +end + +local function read_namespace(name, version, tree) + local namespace + local fd = io.open(path.rock_namespace_file(name, version, tree), "r") + if fd then + namespace = fd:read("*a") + fd:close() + end + return namespace end --- Perform search on a rocks server or tree. --- @param results table: The results table, where keys are package names and +-- @param result_tree table: The result tree, where keys are package names and -- values are tables matching version strings to arrays of -- tables with fields "arch" and "repo". -- @param repo string: The URL of a rocks server or -- the pathname of a rocks tree (as returned by path.rocks_dir()). --- @param query table: A table describing the query in dependency --- format (for example, {name = "filesystem", substring = true, --- constraints = {op = "~>", version = {1,0}}}, arch = "rockspec"). --- If the arch field is omitted, the local architecture (cfg.arch) --- is used. The special value "any" is also recognized, returning all --- matches regardless of architecture. +-- @param query table: a query object. -- @param lua_version string: Lua version in "5.x" format, defaults to installed version. +-- @param is_local boolean -- @return true or, in case of errors, nil, an error message and an optional error code. -function search.manifest_search(results, repo, query, lua_version) - assert(type(results) == "table") +local function manifest_search(result_tree, repo, query, lua_version, is_local) + assert(type(result_tree) == "table") assert(type(repo) == "string") - assert(type(query) == "table") + assert(query:type() == "query") - if query.namespace then + -- FIXME do not add this in local repos + if (not is_local) and query.namespace then repo = repo .. "/manifests/" .. query.namespace end @@ -148,24 +124,34 @@ function search.manifest_search(results, repo, query, lua_version) end for name, versions in pairs(manifest.repository) do for version, items in pairs(versions) do + local namespace = is_local and read_namespace(name, version, repo) or query.namespace for _, item in ipairs(items) do - store_if_match(results, repo, name, version, item.arch, query) + local result = results.new(name, version, repo, item.arch, namespace) + store_if_match(result_tree, result, query) end end end return true end +local function remote_manifest_search(result_tree, repo, query, lua_version) + return manifest_search(result_tree, repo, query, lua_version, false) +end + +function search.local_manifest_search(result_tree, repo, query, lua_version) + return manifest_search(result_tree, repo, query, lua_version, true) +end + --- Search on all configured rocks servers. --- @param query table: A dependency query. +-- @param query table: a query object. -- @param lua_version string: Lua version in "5.x" format, defaults to installed version. -- @return table: A table where keys are package names -- and values are tables matching version strings to arrays of -- tables with fields "arch" and "repo". function search.search_repos(query, lua_version) - assert(type(query) == "table") + assert(query:type() == "query") - local results = {} + local result_tree = {} for _, repo in ipairs(cfg.rocks_servers) do if not cfg.disabled_servers[repo] then if type(repo) == "string" then @@ -176,7 +162,7 @@ function search.search_repos(query, lua_version) if protocol == "file" then mirror = pathname end - local ok, err, errcode = search.manifest_search(results, mirror, query, lua_version) + local ok, err, errcode = remote_manifest_search(result_tree, mirror, query, lua_version) if errcode == "network" then cfg.disabled_servers[repo] = true end @@ -190,16 +176,17 @@ function search.search_repos(query, lua_version) end -- search through rocks in cfg.rocks_provided local provided_repo = "provided by VM or rocks_provided" - for name, versions in pairs(cfg.rocks_provided) do - store_if_match(results, provided_repo, name, versions, "installed", query) + for name, version in pairs(cfg.rocks_provided) do + local result = results.new(name, version, provided_repo, "installed") + store_if_match(result_tree, result, query) end - return results + return result_tree end --- Get the URL for the latest in a set of versions. -- @param name string: The package name to be used in the URL. -- @param versions table: An array of version informations, as stored --- in search results tables. +-- in search result trees. -- @return string or nil: the URL for the latest version if one could -- be picked, or nil. local function pick_latest_version(name, versions) @@ -227,31 +214,32 @@ local function pick_latest_version(name, versions) end -- Find out which other Lua versions provide rock versions matching a query, --- @param query table: A dependency query matching a single rock. +-- @param query table: a query object. -- @return table: array of Lua versions supported, in "5.x" format. local function supported_lua_versions(query) - local results = {} + assert(query:type() == "query") + local result_tree = {} for lua_version in util.lua_versions() do if lua_version ~= cfg.lua_version then if search.search_repos(query, lua_version)[query.name] then - table.insert(results, lua_version) + table.insert(result_tree, lua_version) end end end - return results + return result_tree end --- Attempt to get a single URL for a given search for a rock. --- @param query table: A dependency query matching a single rock. +-- @param query table: a query object. -- @return string or (nil, string): URL for latest matching version -- of the rock if it was found, or nil followed by an error message. function search.find_suitable_rock(query) - assert(type(query) == "table") + assert(query:type() == "query") - local results = search.search_repos(query) - local first_rock = next(results) + local result_tree = search.search_repos(query) + local first_rock = next(result_tree) if not first_rock then if cfg.rocks_provided[query.name] == nil then -- Check if constraints are satisfiable with other Lua versions. @@ -277,7 +265,7 @@ function search.find_suitable_rock(query) end return nil, "No results matching query were found." - elseif next(results, first_rock) then + elseif next(result_tree, first_rock) then -- Shouldn't happen as query must match only one package. return nil, "Several rocks matched query." elseif cfg.rocks_provided[query.name] ~= nil then @@ -285,19 +273,18 @@ function search.find_suitable_rock(query) return nil, "Rock "..query.name.." "..cfg.rocks_provided[query.name].. " was found but it is provided by VM or 'rocks_provided' in the config file." else - return pick_latest_version(query.name, results[first_rock]) + return pick_latest_version(query.name, result_tree[first_rock]) end end --- Print a list of rocks/rockspecs on standard output. --- @param results table: A table where keys are package names and versions --- are tables matching version strings to an array of rocks servers. +-- @param result_tree table: A result tree. -- @param porcelain boolean or nil: A flag to force machine-friendly output. -function search.print_results(results, porcelain) - assert(type(results) == "table") +function search.print_result_tree(result_tree, porcelain) + assert(type(result_tree) == "table") assert(type(porcelain) == "boolean" or not porcelain) - for package, versions in util.sortedpairs(results) do + for package, versions in util.sortedpairs(result_tree) do if not porcelain then util.printout(package) end @@ -331,17 +318,17 @@ function search.act_on_src_or_rockspec(action, name, version, ...) assert(type(name) == "string") assert(type(version) == "string" or not version) - local query = queries.new(name, version, nil, "src|rockspec") + local _, namespace = util.split_namespace(name) + local query = queries.new(name, version, false, "src|rockspec") local url, err = search.find_suitable_rock(query) if not url then return nil, "Could not find a result named "..name..(version and " "..version or "")..": "..err end - return action(url, ...) + return action(url, namespace, ...) end -function search.pick_installed_rock(name, version, given_tree) - local results = {} - local query = queries.new(name, version, true) +function search.pick_installed_rock(query, given_tree) + local result_tree = {} local tree_map = {} local trees = cfg.rocks_trees if given_tree then @@ -350,16 +337,15 @@ function search.pick_installed_rock(name, version, given_tree) for _, tree in ipairs(trees) do local rocks_dir = path.rocks_dir(tree) tree_map[rocks_dir] = tree - search.manifest_search(results, rocks_dir, query) + search.local_manifest_search(result_tree, rocks_dir, query) end - - if not next(results) then -- - return nil,"cannot find package "..name.." "..(version or "").."\nUse 'list' to find installed rocks." + if not next(result_tree) then + return nil, "cannot find package "..tostring(query).."\nUse 'list' to find installed rocks." end - version = nil + local version = nil local repo_url - local package, versions = util.sortedpairs(results)() + local package, versions = util.sortedpairs(result_tree)() --question: what do we do about multiple versions? This should --give us the latest version on the last repo (which is usually the global one) for vs, repositories in util.sortedpairs(versions, vers.compare_versions) do @@ -368,7 +354,7 @@ function search.pick_installed_rock(name, version, given_tree) end local repo = tree_map[repo_url] - return name, version, repo, repo_url + return query.name, version, repo, repo_url end return search diff --git a/src/luarocks/type/rockspec.lua b/src/luarocks/type/rockspec.lua index 5ff48177..214f8e0f 100644 --- a/src/luarocks/type/rockspec.lua +++ b/src/luarocks/type/rockspec.lua @@ -39,7 +39,14 @@ local rockspec_types = { }, dependencies = { platforms = {}, -- recursively defined below - _any = string_1, + _any = { + _type = "string", + _name = "a valid dependency string", + _patterns = { + ["1.0"] = "%s*([a-zA-Z0-9][a-zA-Z0-9%.%-%_]*)%s*([^/]*)", + ["3.0"] = "%s*([a-zA-Z0-9%.%-%_]*/?[a-zA-Z0-9][a-zA-Z0-9%.%-%_]*)%s*([^/]*)", + }, + }, }, supported_platforms = { _any = string_1, @@ -123,8 +130,13 @@ function type_rockspec.check(rockspec, globals) rockspec.rockspec_format = "1.0" end local ok, err = type_check.check_undeclared_globals(globals, rockspec_types) - if not ok then return nil, err end - return type_check.type_check_table(rockspec.rockspec_format, rockspec, rockspec_types, "") + if ok then + ok, err = type_check.type_check_table(rockspec.rockspec_format, rockspec, rockspec_types, "") + end + if ok then + return true + end + return nil, err .. " (rockspec format " .. rockspec.rockspec_format .. ")" end return type_rockspec diff --git a/src/luarocks/type_check.lua b/src/luarocks/type_check.lua index 250c6258..9e3b7c9e 100644 --- a/src/luarocks/type_check.lua +++ b/src/luarocks/type_check.lua @@ -55,9 +55,11 @@ local function type_check_item(version, item, typetbl, context) if item_type ~= "string" then return nil, "Type mismatch on field "..context..": expected a string, got "..item_type end - if typetbl._pattern then - if not item:match("^"..typetbl._pattern.."$") then - return nil, "Type mismatch on field "..context..": invalid value "..item.." does not match '"..typetbl._pattern.."'" + local pattern = (typetbl._patterns and typetbl._patterns[version]) or typetbl._pattern + if pattern then + if not item:match("^"..pattern.."$") then + local what = typetbl._name or ("'"..pattern.."'") + return nil, "Type mismatch on field "..context..": invalid value '"..item.."' does not match " .. what end end elseif expected_type == "table" then diff --git a/src/luarocks/util.lua b/src/luarocks/util.lua index bc8fc049..76b46d55 100644 --- a/src/luarocks/util.lua +++ b/src/luarocks/util.lua @@ -14,6 +14,7 @@ util.split_string = core.split_string util.keys = core.keys util.printerr = core.printerr util.sortedpairs = core.sortedpairs +util.warning = core.warning local unpack = unpack or table.unpack @@ -113,6 +114,7 @@ local supported_flags = { ["lua-libdir"] = true, ["modules"] = true, ["mversion"] = true, + ["namespace"] = "", ["no-refresh"] = true, ["nodeps"] = true, ["old-versions"] = true, @@ -323,12 +325,6 @@ function util.printout(...) io.stdout:write("\n") end ---- Display a warning message. --- @param msg string: the warning message -function util.warning(msg) - util.printerr("Warning: "..msg) -end - function util.title(msg, porcelain, underline) if porcelain then return end util.printout() @@ -397,7 +393,7 @@ local function collect_rockspecs(versions, paths, unnamed_paths, subdir) local fs = require("luarocks.fs") local dir = require("luarocks.dir") local path = require("luarocks.path") - local vers = require("luarocks.vers") + local vers = require("luarocks.core.vers") if fs.is_dir(subdir) then for file in fs.dir(subdir) do @@ -459,4 +455,40 @@ function util.LQ(s) return ("%q"):format(s) end +--- Normalize the --namespace flag and the user/rock syntax for namespaces. +-- If a namespace is given in user/rock syntax, update the --namespace flag; +-- If a namespace is given in --namespace flag, update the user/rock syntax. +-- In case of conflicts, the user/rock syntax takes precedence. +function util.adjust_name_and_namespace(name, flags) + assert(type(name) == "string" or not name) + assert(type(flags) == "table") + + if not name then + return + elseif name:match("%.rockspec$") or name:match("%.rock$") then + return name + end + + local namespace + name, namespace = util.split_namespace(name) + if namespace then + flags["namespace"] = namespace + end + if flags["namespace"] then + name = flags["namespace"] .. "/" .. name + end + return name:lower() +end + +-- Split name and namespace of a package name. +-- @param name a name that may be in "namespace/name" format +-- @return string, string? - name and optionally a namespace +function util.split_namespace(name) + local p1, p2 = name:match("^([^/]+)/([^/]+)$") + if p1 then + return p2, p1 + end + return name +end + return util diff --git a/src/luarocks/vers.lua b/src/luarocks/vers.lua deleted file mode 100644 index c1dfbd39..00000000 --- a/src/luarocks/vers.lua +++ /dev/null @@ -1,126 +0,0 @@ - ---- Dependency format handling functions. --- Dependencies are represented in LuaRocks through strings with --- a package name followed by a comma-separated list of constraints. --- Each constraint consists of an operator and a version number. --- In this string format, version numbers are represented as --- naturally as possible, like they are used by upstream projects --- (e.g. "2.0beta3"). Internally, LuaRocks converts them to a purely --- numeric representation, allowing comparison following some --- "common sense" heuristics. The precise specification of the --- comparison criteria is the source code of this module. -local vers = {} - -local core = require("luarocks.core.vers") - -vers.parse_version = core.parse_version -vers.compare_versions = core.compare_versions -vers.match_constraints = core.match_constraints - ---- Check if rockspec format version satisfies version requirement. --- @param rockspec table: The rockspec table. --- @param version string: required version. --- @return boolean: true if rockspec format matches version or is newer, false otherwise. -function vers.format_is_at_least(rockspec, version) - local rockspec_format = rockspec.rockspec_format or "1.0" - return vers.parse_version(rockspec_format) >= vers.parse_version(version) -end - -local operators = { - ["=="] = "==", - ["~="] = "~=", - [">"] = ">", - ["<"] = "<", - [">="] = ">=", - ["<="] = "<=", - ["~>"] = "~>", - -- plus some convenience translations - [""] = "==", - ["="] = "==", - ["!="] = "~=" -} - ---- Consumes a constraint from a string, converting it to table format. --- For example, a string ">= 1.0, > 2.0" is converted to a table in the --- format {op = ">=", version={1,0}} and the rest, "> 2.0", is returned --- back to the caller. --- @param input string: A list of constraints in string format. --- @return (table, string) or nil: A table representing the same --- constraints and the string with the unused input, or nil if the --- input string is invalid. -local function parse_constraint(input) - assert(type(input) == "string") - - local no_upgrade, op, version, rest = input:match("^(@?)([<>=~!]*)%s*([%w%.%_%-]+)[%s,]*(.*)") - local _op = operators[op] - version = vers.parse_version(version) - if not _op then - return nil, "Encountered bad constraint operator: '"..tostring(op).."' in '"..input.."'" - end - if not version then - return nil, "Could not parse version from constraint: '"..input.."'" - end - return { op = _op, version = version, no_upgrade = no_upgrade=="@" and true or nil }, rest -end - ---- Convert a list of constraints from string to table format. --- For example, a string ">= 1.0, < 2.0" is converted to a table in the format --- {{op = ">=", version={1,0}}, {op = "<", version={2,0}}}. --- Version tables use a metatable allowing later comparison through --- relational operators. --- @param input string: A list of constraints in string format. --- @return table or nil: A table representing the same constraints, --- or nil if the input string is invalid. -function vers.parse_constraints(input) - assert(type(input) == "string") - - local constraints, oinput, constraint = {}, input - while #input > 0 do - constraint, input = parse_constraint(input) - if constraint then - table.insert(constraints, constraint) - else - return nil, "Failed to parse constraint '"..tostring(oinput).."' with error: ".. input - end - end - return constraints -end - ---- Convert a dependency from string to table format. --- For example, a string "foo >= 1.0, < 2.0" --- is converted to a table in the format --- {name = "foo", constraints = {{op = ">=", version={1,0}}, --- {op = "<", version={2,0}}}}. Version tables use a metatable --- allowing later comparison through relational operators. --- @param dep string: A dependency in string format --- as entered in rockspec files. --- @return table or nil: A table representing the same dependency relation, --- or nil if the input string is invalid. -function vers.parse_dep(dep) - assert(type(dep) == "string") - - local name, rest = dep:match("^%s*([a-zA-Z0-9][a-zA-Z0-9%.%-%_]*)%s*(.*)") - if not name then return nil, "failed to extract dependency name from '"..tostring(dep).."'" end - local constraints, err = vers.parse_constraints(rest) - if not constraints then return nil, err end - return { name = name, constraints = constraints } -end - ---- Convert a dependency in table format to a string. --- @param dep table: The dependency in table format --- @return string: The dependency information pretty-printed as a string. -function vers.show_dep(dep) - assert(type(dep) == "table") - - if #dep.constraints > 0 then - local pretty = {} - for _, c in ipairs(dep.constraints) do - table.insert(pretty, c.op .. " " .. tostring(c.version)) - end - return dep.name.." "..table.concat(pretty, ", ") - else - return dep.name - end -end - -return vers -- cgit v1.2.3-55-g6feb