diff options
| -rw-r--r-- | tests/actions/addon/test.lua | 114 | ||||
| -rw-r--r-- | xmake/actions/addon/main.lua | 137 | ||||
| -rw-r--r-- | xmake/actions/addon/xmake.lua | 4 | ||||
| -rw-r--r-- | xmake/core/package/addon.lua | 19 | ||||
| -rw-r--r-- | xmake/core/sandbox/modules/import/core/package/addon.lua | 1 | ||||
| -rw-r--r-- | xmake/modules/package/manager/xmake/search_package.lua | 4 | ||||
| -rw-r--r-- | xmake/modules/private/action/require/search.lua | 8 | ||||
| -rw-r--r-- | xmake/modules/private/xrepo/action/remove.lua | 18 | ||||
| -rw-r--r-- | xmake/modules/private/xrepo/action/search.lua | 6 | ||||
| -rw-r--r-- | xmake/modules/private/xrepo/quick_search/cache.lua | 68 |
10 files changed, 230 insertions, 149 deletions
diff --git a/tests/actions/addon/test.lua b/tests/actions/addon/test.lua index 17a5dc33c..d393c0545 100644 --- a/tests/actions/addon/test.lua +++ b/tests/actions/addon/test.lua @@ -68,6 +68,9 @@ function _mock_repo(basenames) cache.repositories[reponame] = {repodir} io.save(cachefile, cache) + -- we need to clear the quick search cache, it will be rebuilt on the next search + os.tryrm(path.join(global.cachedir(), "quick_search")) + local function cleanup() for _, name in ipairs(names) do os.tryrm(path.join(global.directory(), "addons", name)) @@ -78,49 +81,65 @@ function _mock_repo(basenames) cache.repositories[reponame] = nil end io.save(cachefile, cache) + os.tryrm(path.join(global.cachedir(), "quick_search")) os.tryrm(repodir) end return reponame, names, cleanup end +-- run the given function with a mocked repository, we always clean it up even if the test fails +function _with_repo(basenames, func) + local reponame, names, cleanup = _mock_repo(basenames) + try + { + function () + func(reponame, names) + end, + finally + { + cleanup + } + } +end + -- install an addon from a repository, by plain name and by repo@name function test_install_from_repo(t) - local reponame, names, cleanup = _mock_repo({"hello"}) - local name = names[1] + _with_repo({"hello"}, function (reponame, names) + local name = names[1] - -- install by plain name (searched across all repositories) - os.runv("xmake", {"addon", "--install", "-y", name}) - t:require(os.iorunv("xmake", {name}):find(name, 1, true)) + -- install by plain name (searched across all repositories) + os.runv("xmake", {"addon", "--install", "-y", name}) + t:require(os.iorunv("xmake", {name}):find(name, 1, true)) - -- reinstall by repo@name - os.runv("xmake", {"addon", "--remove", name}) - os.runv("xmake", {"addon", "--install", "-y", reponame .. "@" .. name}) - t:require(os.iorunv("xmake", {name}):find(name, 1, true)) + -- reinstall by repo@name + os.runv("xmake", {"addon", "--remove", name}) + os.runv("xmake", {"addon", "--install", "-y", reponame .. "@" .. name}) + t:require(os.iorunv("xmake", {name}):find(name, 1, true)) - os.runv("xmake", {"addon", "--remove", name}) - cleanup() + os.runv("xmake", {"addon", "--remove", name}) + end) end -- the templates of an installed addon can be used by `xmake create` function test_install_templates(t) - local _, names, cleanup = _mock_repo({"hello"}) - local name = names[1] - os.runv("xmake", {"addon", "--install", "-y", name}) + _with_repo({"hello"}, function (_, names) + local name = names[1] + os.runv("xmake", {"addon", "--install", "-y", name}) - -- this template should be listed and grouped by its addon name - local out = os.iorunv("xmake", {"create", "--list"}) - t:require(out:find(name, 1, true)) + -- this template should be listed and grouped by its addon name + local out = os.iorunv("xmake", {"create", "--list"}) + t:require(out:find(name, 1, true)) - -- we can create a new project from it - local projectdir = os.tmpfile() .. ".addon-project" - os.tryrm(projectdir) - os.runv("xmake", {"create", "-l", "c", "-t", name, "-P", projectdir}) - t:require(os.isfile(path.join(projectdir, "xmake.lua"))) - t:require(os.isfile(path.join(projectdir, "src", "main.c"))) + -- we can create a new project from it + local projectdir = os.tmpfile() .. ".addon-project" + os.tryrm(projectdir) + os.runv("xmake", {"create", "-l", "c", "-t", name, "-P", projectdir}) + t:require(os.isfile(path.join(projectdir, "xmake.lua"))) + t:require(os.isfile(path.join(projectdir, "src", "main.c"))) - os.tryrm(projectdir) - os.runv("xmake", {"addon", "--remove", name}) - cleanup() + os.tryrm(projectdir) + os.runv("xmake", {"addon", "--remove", name}) + end) end -- install an addon from a local directory, then remove it @@ -140,24 +159,39 @@ function test_install_from_local(t) os.tryrm(path.directory(dir)) end --- --list shows the installed and available addons +-- --list shows the installed addons and their payloads function test_list(t) - local _, names, cleanup = _mock_repo({"hello", "world"}) + _with_repo({"hello", "world"}, function (_, names) + + -- install the first addon, leave the second only available + os.runv("xmake", {"addon", "--install", "-y", names[1]}) + local out = os.iorunv("xmake", {"addon", "--list"}) + t:require(out:find("the installed addons:", 1, true)) + t:require(out:find(names[1], 1, true)) - -- install the first addon, leave the second only available - os.runv("xmake", {"addon", "--install", "-y", names[1]}) - local out = os.iorunv("xmake", {"addon", "--list"}) - t:require(out:find("the installed addons:", 1, true)) - t:require(out:find(names[1], 1, true)) - t:require(out:find(names[2], 1, true)) - t:require(out:find("xmake addon --install " .. names[2], 1, true)) + -- the payloads of the installed addon should be shown, e.g. (plugins, templates) + t:require(out:find("plugins", 1, true)) + t:require(out:find("templates", 1, true)) + + -- the addon which is not installed should be shown in the available addons + t:require(out:find("the available addons:", 1, true)) + t:require(out:find(names[2], 1, true)) + + os.runv("xmake", {"addon", "--remove", names[1]}) + end) +end - -- the payloads of the installed addon should be shown, e.g. (latest, plugins, templates) - t:require(out:find("plugins", 1, true)) - t:require(out:find("templates", 1, true)) +-- --search finds the addons in the repositories, it reuses `xrepo search --addon` +function test_search(t) + _with_repo({"hello"}, function (_, names) + local name = names[1] + local out = os.iorunv("xmake", {"addon", "--search", name}) + t:require(out:find(name, 1, true)) - os.runv("xmake", {"addon", "--remove", names[1]}) - cleanup() + -- the addons should not be found by the package search + local packages_out = os.iorunv("xrepo", {"search", name}) + t:require_not(packages_out:find(name, 1, true)) + end) end -- invalid installs should fail diff --git a/xmake/actions/addon/main.lua b/xmake/actions/addon/main.lua index 52cb2376b..f3da68d8e 100644 --- a/xmake/actions/addon/main.lua +++ b/xmake/actions/addon/main.lua @@ -21,13 +21,16 @@ -- imports import("core.base.option") import("core.package.addon") -import("core.package.repository") import("devel.git") import("private.action.require.impl.environment") +import("private.action.require.impl.search_packages") -- the version directory name for the addons installed from git urls or local directories local LOCALVERSION = "latest" +-- the maximum number of the available addons shown by `--list` +local LISTLIMIT = 10 + -- validate an addon directory name function _check_addon_name(name) assert(type(name) == "string" and name ~= "" and name ~= "." and not name:find("..", 1, true) and not name:find("[/\\:]"), "invalid addon name(%s)!", name) @@ -46,23 +49,9 @@ function _get_addondir(name, version) return addondir end --- get local and global repositories, with local taking precedence -function _repositories() - return table.join(repository.repositories({global = false}), repository.repositories({global = true})) -end - --- install an addon from the given repository or the first repository containing it -function _install_from_repo(name, reponame) - - -- check addon name - _check_addon_name(name) - - -- do install - local installname = name - if reponame then - installname = reponame .. "@" .. name - end - local argv = {"lua", "private.xrepo", "install", "--addon"} +-- run the given xrepo action for the addons, e.g. install, remove, search +function _xrepo(action, names) + local argv = {"lua", "private.xrepo", action, "--addon"} -- we need to pass the common options to the sub-process, e.g. -y, -v, -D if option.get("yes") then table.insert(argv, "-y") @@ -73,10 +62,16 @@ function _install_from_repo(name, reponame) if option.get("diagnosis") then table.insert(argv, "-D") end - table.insert(argv, installname) + table.join2(argv, names) os.execv(os.programfile(), argv) end +-- install an addon from the given repository or the first repository containing it +function _install_from_repo(name, reponame) + _check_addon_name(name) + _xrepo("install", {reponame and (reponame .. "@" .. name) or name}) +end + -- install a single addon from a source directory (as the given name, default to the directory name) function _install_from_local(dir, name) assert(os.isdir(dir), "addon path(%s) not found!", dir) @@ -148,27 +143,16 @@ function _install() environment.leave() end --- remove the given installed addon +-- remove the given installed addons function _remove() local names = assert(option.get("addons"), "please specify the addon name to be removed!") - assert(#names == 1, "please specify only one addon name to be removed!") - local name = names[1] - local dir = _get_addondir(name) - assert(os.isdir(dir), "addon(%s) not found!", name) - os.rmdir(dir) - addon.unregister(name) - cprint("${color.success}remove ${bright}%s${clear} ok!", name) + _xrepo("remove", names) end --- get the description of an addon from its package description file -function _addon_description(dir) - local filepath = path.join(dir, "xmake.lua") - if os.isfile(filepath) then - local content = io.readfile(filepath) - if content then - return content:match("set_description%s*%(\"(.-)\"%)") - end - end +-- search the addons from the repositories +function _search() + local patterns = assert(option.get("addons"), "please specify the addon name pattern to be searched!") + _xrepo("search", patterns) end -- collect the installed addons from the addons registry @@ -186,70 +170,57 @@ function _collect_installed_addons() return entries end --- collect the addons in the given repository, they follow the packages layout (addons/<first-letter>/<name>) -function _collect_repo_addons(root, seen) - local entries = {} - for _, dir in ipairs(os.dirs(path.join(root, "*", "*"))) do - local name = path.filename(dir) - if os.isfile(path.join(dir, "xmake.lua")) and not seen[name] then - seen[name] = true - table.insert(entries, {name = name, description = _addon_description(dir)}) - end +-- print an addon entry, e.g. -> serial-monitor v1.0.1: monitor the serial port output (in xmake-repo) +function _print_addon(entry, suffix) + local title = entry.name + if entry.version then + title = title .. " " .. entry.version end - return entries + local description = entry.description and (": " .. entry.description) or "" + cprint(" ${color.dump.reference}->${clear} ${color.dump.string}%s${clear}%s%s", title, description, suffix or "") end --- print an addon entry with its description aligned on the right -function _print_addon(name, description, width, note) - local suffix = description or "" - if note then - suffix = suffix ~= "" and (suffix .. " " .. note) or note - end - if suffix ~= "" then - local padding = math.max(width - #name, 1) - cprint(" ${color.dump.string}%s${clear}%s%s", name, (" "):rep(padding), suffix) - else - cprint(" ${color.dump.string}%s${clear}", name) +-- get the addons in the repositories, we reuse the packages search here +function _collect_repo_addons(exclude) + local entries = {} + for _, results in pairs(search_packages({"*"}, {kind = "addon", description = false})) do + for _, result in ipairs(results) do + if not exclude[result.name] then + table.insert(entries, result) + end + end end + table.sort(entries, function (a, b) return a.name < b.name end) + return entries end -- list all addons function _list() - local seen = {} - local installed = _collect_installed_addons() - for _, entry in ipairs(installed) do - seen[entry.name] = true - end - local avail = {} - for _, repo in ipairs(_repositories()) do - table.join2(avail, _collect_repo_addons(path.join(repo:directory(), "addons"), seen)) - end - -- compute the alignment width from all addon names - local width = 0 - for _, entries in ipairs({installed, avail}) do - for _, entry in ipairs(entries) do - width = math.max(width, #entry.name + 4) - end - end - - -- installed addons + -- show the installed addons + local installed = _collect_installed_addons() + local exclude = {} cprint("${bright}the installed addons:${clear}") if #installed > 0 then for _, entry in ipairs(installed) do - local note = string.format("(%s, %s)", entry.version, table.concat(entry.payloads, ", ")) - _print_addon(entry.name, entry.description, width, note) + exclude[entry.name] = true + _print_addon(entry, string.format(" ${dim}(%s)${clear}", table.concat(entry.payloads, ", "))) end else print(" (none)") end - -- addons available in repositories (not yet installed) - cprint("${bright}available in configured repositories:${clear}") + -- show the addons in the repositories, we only show the first ones if there are too many + local avail = _collect_repo_addons(exclude) + cprint("${bright}the available addons:${clear} ${dim}(run `xmake addon --install <name>` to install," .. + " `--search <pattern>` to search)${clear}") if #avail > 0 then - for _, entry in ipairs(avail) do - local note = string.format("(run xmake addon --install %s to install)", entry.name) - _print_addon(entry.name, entry.description, width, note) + for idx, entry in ipairs(avail) do + if idx > LISTLIMIT then + cprint(" ${dim}... and %d more${clear}", #avail - LISTLIMIT) + break + end + _print_addon(entry, entry.reponame and string.format(" ${dim}(in %s)${clear}", entry.reponame) or nil) end else print(" (none)") @@ -269,6 +240,8 @@ function main() _remove() elseif option.get("list") then _list() + elseif option.get("search") then + _search() elseif option.get("clear") then _clear() end diff --git a/xmake/actions/addon/xmake.lua b/xmake/actions/addon/xmake.lua index 8394c110b..240fef5bf 100644 --- a/xmake/actions/addon/xmake.lua +++ b/xmake/actions/addon/xmake.lua @@ -26,7 +26,8 @@ task("addon") description = "Manage addons of xmake.", options = { {'i', "install", "k", nil, "Install addons."}, - {'r', "remove", "k", nil, "Remove the given installed addon."}, + {'r', "remove", "k", nil, "Remove the given installed addons."}, + {'s', "search", "k", nil, "Search the addons from the repositories."}, {'l', "list", "k", nil, "List all installed addons."}, {'c', "clear", "k", nil, "Clear all installed addons."}, {nil, "addons", "vs", nil, "The addon paths, urls or names.", @@ -38,6 +39,7 @@ task("addon") " $ xmake addon --install xmake-repo@serial-monitor", " $ xmake addon --install serial-monitor", " $ xmake addon --remove serial-monitor", + " $ xmake addon --search serial", " $ xmake addon --list"} } } diff --git a/xmake/core/package/addon.lua b/xmake/core/package/addon.lua index d433bdf8a..78830d266 100644 --- a/xmake/core/package/addon.lua +++ b/xmake/core/package/addon.lua @@ -182,6 +182,25 @@ function addon.register(name, version, opt) addon._save(addons) end +-- remove the given installed addon +-- +-- @param name the addon name +-- @return true or false and errors +-- +function addon.remove(name) + local dirname = addon.dirname(name) + local installdir = path.join(addon.installdir(), dirname) + if not os.isdir(installdir) then + return false, string.format("addon(%s) not found!", name) + end + local ok, errors = os.rm(installdir) + if not ok then + return false, errors + end + addon.unregister(name) + return true +end + -- unregister the given addon function addon.unregister(name) local dirname = addon.dirname(name) diff --git a/xmake/core/sandbox/modules/import/core/package/addon.lua b/xmake/core/sandbox/modules/import/core/package/addon.lua index ecbd95f2b..2885bd45f 100644 --- a/xmake/core/sandbox/modules/import/core/package/addon.lua +++ b/xmake/core/sandbox/modules/import/core/package/addon.lua @@ -36,6 +36,7 @@ sandbox_core_package_addon.addons = addon.addons sandbox_core_package_addon.addondir = addon.addondir sandbox_core_package_addon.register = addon.register sandbox_core_package_addon.unregister = addon.unregister +sandbox_core_package_addon.remove = addon.remove sandbox_core_package_addon.rescan = addon.rescan sandbox_core_package_addon.clear = addon.clear diff --git a/xmake/modules/package/manager/xmake/search_package.lua b/xmake/modules/package/manager/xmake/search_package.lua index 32991ae3e..a5ed3caf8 100644 --- a/xmake/modules/package/manager/xmake/search_package.lua +++ b/xmake/modules/package/manager/xmake/search_package.lua @@ -23,7 +23,7 @@ import("core.base.semver") import("private.xrepo.quick_search.cache") function _search_package(packages, name, opt) - for _, packageinfo in ipairs(cache.find(name, {description = opt.description ~= false})) do + for _, packageinfo in ipairs(cache.find(name, {description = opt.description ~= false, kind = opt.kind})) do local packagename = packageinfo.name local packagedata = packageinfo.data @@ -59,7 +59,7 @@ end -- search package using the xmake package manager -- -- @param name the package name with pattern --- @param opt the options, e.g. {require_version = "1.x"} +-- @param opt the options, e.g. {require_version = "1.x", kind = "addon"} -- function main(name, opt) opt = opt or {} diff --git a/xmake/modules/private/action/require/search.lua b/xmake/modules/private/action/require/search.lua index e714c6c19..a0f98e0d9 100644 --- a/xmake/modules/private/action/require/search.lua +++ b/xmake/modules/private/action/require/search.lua @@ -20,6 +20,7 @@ -- imports import("core.base.task") +import("core.base.option") import("private.action.require.impl.utils.filter") import("private.action.require.impl.repository") import("private.action.require.impl.environment") @@ -41,11 +42,14 @@ function main(names) task.run("repo", {update = true}) end + -- we only search the addon packages if `--addon` is enabled + local kind = option.get("addon") and "addon" or nil + -- show title - print("The package names:") + print(kind == "addon" and "The addon names:" or "The package names:") -- search packages - for name, packages in pairs(search_packages(names)) do + for name, packages in pairs(search_packages(names, {kind = kind})) do if #packages > 0 then -- show name diff --git a/xmake/modules/private/xrepo/action/remove.lua b/xmake/modules/private/xrepo/action/remove.lua index ed92a83b8..adfec92c0 100644 --- a/xmake/modules/private/xrepo/action/remove.lua +++ b/xmake/modules/private/xrepo/action/remove.lua @@ -20,6 +20,7 @@ -- imports import("core.base.option") +import("core.package.addon") import("private.action.require.impl.remove_packages", {alias = "remove_all_packages"}) -- get menu options @@ -44,6 +45,9 @@ function menu_options() {nil, "toolchain", "kv", nil, "Set the toolchain name." }, {nil, "toolchain_host", "kv", nil, "Set the host toolchain name." }, { }, + {nil, "addon", "k", nil, "Remove the given installed addon packages.", + "e.g.", + " - xrepo remove --addon serial-monitor" }, {nil, "all", "k", nil, "Remove all packages and ignore extra package configs.", "If `--all` is enabled, the package name parameter will support lua pattern", "e.g.", @@ -193,10 +197,22 @@ function _remove_packages(packages) os.vexecv(os.programfile(), require_argv) end +-- remove the given installed addons +function _remove_addons(names) + for _, name in ipairs(names) do + local ok, errors = addon.remove(name) + assert(ok, errors) + cprint("${color.success}remove ${bright}%s${clear} ok!", name) + end +end + -- main entry function main() local packages = option.get("packages") - if option.get("all") then + if option.get("addon") then + assert(packages, "please specify the addons to be removed.") + _remove_addons(packages) + elseif option.get("all") then remove_all_packages(packages) elseif packages then _remove_packages(packages) diff --git a/xmake/modules/private/xrepo/action/search.lua b/xmake/modules/private/xrepo/action/search.lua index d389e3833..c532af44e 100644 --- a/xmake/modules/private/xrepo/action/search.lua +++ b/xmake/modules/private/xrepo/action/search.lua @@ -30,6 +30,9 @@ function menu_options() -- menu options local options = { + {nil, "addon", "k", nil, "Search the addon packages from <repository>/addons/", + "e.g.", + " - xrepo search --addon serial"}, {nil, "packages", "vs", nil, "The packages list (support lua pattern).", "e.g.", " - xrepo search zlib boost", @@ -80,6 +83,9 @@ function _search_packages(packages) if option.get("diagnosis") then table.insert(require_argv, "-D") end + if option.get("addon") then + table.insert(require_argv, "--addon") + end table.join2(require_argv, packages) os.vexecv(os.programfile(), require_argv) end diff --git a/xmake/modules/private/xrepo/quick_search/cache.lua b/xmake/modules/private/xrepo/quick_search/cache.lua index cf75a212d..43af417a8 100644 --- a/xmake/modules/private/xrepo/quick_search/cache.lua +++ b/xmake/modules/private/xrepo/quick_search/cache.lua @@ -25,20 +25,36 @@ import("private.action.require.impl.repository") local cache = globalcache.cache("quick_search") +-- get the cache key of the given package +-- +-- @note the addons are stored with the `addon::` prefix, +-- because an addon and a package may have the same name +-- +function _cachekey(packagename, kind) + return kind == "addon" and ("addon::" .. packagename) or packagename +end + -- search package directories from repositories +-- +-- the packages are stored in <repodir>/packages/<first-letter>/<name>, +-- and the addons are stored in <repodir>/addons/<first-letter>/<name> +-- function _list_package_dirs() -- find the package directories from all repositories local unique = {} local packageinfos = {} for _, repo in ipairs(repository.repositories()) do - for _, file in ipairs(os.files(path.join(repo:directory(), "packages", "*", "*", "xmake.lua"))) do - local dir = path.directory(file) - local subdirname = path.basename(path.directory(dir)) - if #subdirname == 1 then -- ignore l/luajit/port/xmake.lua - local packagename = path.filename(dir) - if not unique[packagename] then - table.insert(packageinfos, {name = packagename, repo = repo, packagedir = dir}) - unique[packagename] = true + for _, rootinfo in ipairs({{rootdir = "packages"}, {rootdir = "addons", kind = "addon"}}) do + for _, file in ipairs(os.files(path.join(repo:directory(), rootinfo.rootdir, "*", "*", "xmake.lua"))) do + local dir = path.directory(file) + local subdirname = path.basename(path.directory(dir)) + if #subdirname == 1 then -- ignore l/luajit/port/xmake.lua + local packagename = path.filename(dir) + local cachekey = _cachekey(packagename, rootinfo.kind) + if not unique[cachekey] then + table.insert(packageinfos, {name = packagename, kind = rootinfo.kind, repo = repo, packagedir = dir}) + unique[cachekey] = true + end end end end @@ -57,7 +73,9 @@ end function update() for _, packageinfo in ipairs(_list_package_dirs()) do local package = core_package.load_from_repository(packageinfo.name, packageinfo.packagedir, {repo = packageinfo.repo}) - cache:set(packageinfo.name, { + cache:set(_cachekey(packageinfo.name, packageinfo.kind), { + name = packageinfo.name, + kind = packageinfo.kind, reponame = package:repo() and package:repo():name(), description = package:description(), versions = package:versions(), @@ -79,22 +97,30 @@ function get() end -- find package +-- +-- @param name the package name (support lua pattern) +-- @param opt the options, e.g. {prefix = true, description = true, kind = "addon"} +-- function find(name, opt) _init() opt = opt or {} local list_result = {} - for packagename, packagedata in pairs(cache:data()) do - local found = false - if opt.prefix then - found = packagename:startswith(name) - else - found = packagename:find(path.pattern(name)) - end - if not found and opt.description and packagedata.description and packagedata.description:find(name) then - found = true - end - if found then - table.insert(list_result, {name = packagename, data = packagedata}) + for cachekey, packagedata in pairs(cache:data()) do + -- we only search the packages with the given kind, e.g. nil (package), "addon" + local packagename = packagedata.name or cachekey + if packagedata.kind == opt.kind then + local found = false + if opt.prefix then + found = packagename:startswith(name) + else + found = packagename:find(path.pattern(name)) + end + if not found and opt.description and packagedata.description and packagedata.description:find(name) then + found = true + end + if found then + table.insert(list_result, {name = packagename, data = packagedata}) + end end end return list_result |
