From 21f1b0efbf5441e5ec82a2caa84984a77ad7e7eb Mon Sep 17 00:00:00 2001 From: Saikari Date: Sun, 26 Jul 2026 22:50:42 +0300 Subject: Added tests for plugins --- tests/plugins/repository/test.lua | 56 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 tests/plugins/repository/test.lua (limited to 'tests/plugins') diff --git a/tests/plugins/repository/test.lua b/tests/plugins/repository/test.lua new file mode 100644 index 000000000..0f7b1cc60 --- /dev/null +++ b/tests/plugins/repository/test.lua @@ -0,0 +1,56 @@ +function main() + local prog = os.programfile() + local gd = os.tmpfile() .. ".gd" + io.writefile(path.join(gd, ".xmake", "repositories", "xmake-repo", "plugins", "hello-world", "xmake.lua"), [[ +task("hello-world") + set_category("plugin") + on_run("main") + set_menu {usage = "xmake hello-world"} +]]) + io.writefile(path.join(gd, ".xmake", "repositories", "xmake-repo", "plugins", "hello-world", "main.lua"), [[ +function main() print("repo-ok") end +]]) + local out = os.iorunv(prog, {"hello-world"}, {envs = {XMAKE_GLOBALDIR = gd}}) + assert(out:find("repo%-ok", 1, true)) + local out = os.iorunv(prog, {"hello-world"}, {envs = {XMAKE_PROGRAM_DIR = os.programdir(), XMAKE_GLOBALDIR = gd}}) + assert(out:find("xrepo", 1, true)) + + local md = os.tmpfile() .. ".md" + io.writefile(path.join(md, ".xmake", "plugins", "manual-plugin", "xmake.lua"), [[ +task("manual-plugin") + set_category("plugin") + on_run("main") + set_menu {usage = "xmake manual-plugin"} +]]) + io.writefile(path.join(md, ".xmake", "plugins", "manual-plugin", "main.lua"), [[ +function main() print("manual") end +]]) + out = os.iorunv(prog, {"plugin", "--list"}, {envs = {XMAKE_GLOBALDIR = md}}) + assert(out:find("manual%-plugin", 1, true)) + out = os.iorunv(prog, {"plugin", "--list"}, {envs = {XMAKE_GLOBALDIR = gd}}) + assert(out:find("hello%-world", 1, true)) + + out = os.iorunv(prog, {"plugin", "--remove", "manual-plugin"}, {envs = {XMAKE_GLOBALDIR = md}}) + assert(out:find("remove plugin", 1, true)) + out = os.iorunv(prog, {"plugin", "--list"}, {envs = {XMAKE_GLOBALDIR = md}}) + assert(not out:find("manual%-plugin", 1, true)) + + local ld = os.tmpfile() .. ".ld" + io.writefile(path.join(ld, "plugins", "hello-world", "xmake.lua"), [[ +task("hello-world") + set_category("plugin") + on_run("main") + set_menu {usage = "xmake hello-world"} +]]) + io.writefile(path.join(ld, "plugins", "hello-world", "main.lua"), [[ +function main() print("local-ok") end +]]) + out = os.iorunv(prog, {"hello-world"}, {envs = {XMAKE_GLOBALDIR = gd, XMAKE_MAIN_REPO = ld}}) + assert(out:find("local%-ok", 1, true)) + out = os.iorunv(prog, {"hello-world"}, {envs = {XMAKE_GLOBALDIR = gd}}) + assert(out:find("repo%-ok", 1, true)) + + os.tryrm(gd) + os.tryrm(md) + os.tryrm(ld) +end -- cgit v1.3.1 From b4e04c99d5e3d3a09ddec4ac1ceff016e05c1c64 Mon Sep 17 00:00:00 2001 From: Saikari Date: Sun, 26 Jul 2026 23:07:59 +0300 Subject: Refactor test script to improve environment variable handling and streamline plugin execution --- tests/plugins/repository/test.lua | 60 ++++++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 23 deletions(-) (limited to 'tests/plugins') diff --git a/tests/plugins/repository/test.lua b/tests/plugins/repository/test.lua index 0f7b1cc60..07d11eb37 100644 --- a/tests/plugins/repository/test.lua +++ b/tests/plugins/repository/test.lua @@ -1,54 +1,68 @@ function main() - local prog = os.programfile() + -- backup existing env vars + local prev_globaldir = os.getenv("XMAKE_GLOBALDIR") + local prev_main_repo = os.getenv("XMAKE_MAIN_REPO") + local gd = os.tmpfile() .. ".gd" - io.writefile(path.join(gd, ".xmake", "repositories", "xmake-repo", "plugins", "hello-world", "xmake.lua"), [[ + io.writefile(gd .. "/.xmake/repositories/xmake-repo/plugins/hello-world/xmake.lua", [[ task("hello-world") set_category("plugin") on_run("main") set_menu {usage = "xmake hello-world"} ]]) - io.writefile(path.join(gd, ".xmake", "repositories", "xmake-repo", "plugins", "hello-world", "main.lua"), [[ + io.writefile(gd .. "/.xmake/repositories/xmake-repo/plugins/hello-world/main.lua", [[ function main() print("repo-ok") end ]]) - local out = os.iorunv(prog, {"hello-world"}, {envs = {XMAKE_GLOBALDIR = gd}}) - assert(out:find("repo%-ok", 1, true)) - local out = os.iorunv(prog, {"hello-world"}, {envs = {XMAKE_PROGRAM_DIR = os.programdir(), XMAKE_GLOBALDIR = gd}}) - assert(out:find("xrepo", 1, true)) + + os.setenv("XMAKE_GLOBALDIR", gd) + os.exec("xmake hello-world") + + os.exec("xmake plugin --install hello-world") local md = os.tmpfile() .. ".md" - io.writefile(path.join(md, ".xmake", "plugins", "manual-plugin", "xmake.lua"), [[ + io.writefile(md .. "/.xmake/plugins/manual-plugin/xmake.lua", [[ task("manual-plugin") set_category("plugin") on_run("main") set_menu {usage = "xmake manual-plugin"} ]]) - io.writefile(path.join(md, ".xmake", "plugins", "manual-plugin", "main.lua"), [[ + io.writefile(md .. "/.xmake/plugins/manual-plugin/main.lua", [[ function main() print("manual") end ]]) - out = os.iorunv(prog, {"plugin", "--list"}, {envs = {XMAKE_GLOBALDIR = md}}) - assert(out:find("manual%-plugin", 1, true)) - out = os.iorunv(prog, {"plugin", "--list"}, {envs = {XMAKE_GLOBALDIR = gd}}) - assert(out:find("hello%-world", 1, true)) - out = os.iorunv(prog, {"plugin", "--remove", "manual-plugin"}, {envs = {XMAKE_GLOBALDIR = md}}) - assert(out:find("remove plugin", 1, true)) - out = os.iorunv(prog, {"plugin", "--list"}, {envs = {XMAKE_GLOBALDIR = md}}) - assert(not out:find("manual%-plugin", 1, true)) + os.setenv("XMAKE_GLOBALDIR", md) + os.exec("xmake plugin --list") + os.exec("xmake plugin --remove manual-plugin") local ld = os.tmpfile() .. ".ld" - io.writefile(path.join(ld, "plugins", "hello-world", "xmake.lua"), [[ + io.writefile(ld .. "/plugins/hello-world/xmake.lua", [[ task("hello-world") set_category("plugin") on_run("main") set_menu {usage = "xmake hello-world"} ]]) - io.writefile(path.join(ld, "plugins", "hello-world", "main.lua"), [[ + io.writefile(ld .. "/plugins/hello-world/main.lua", [[ function main() print("local-ok") end ]]) - out = os.iorunv(prog, {"hello-world"}, {envs = {XMAKE_GLOBALDIR = gd, XMAKE_MAIN_REPO = ld}}) - assert(out:find("local%-ok", 1, true)) - out = os.iorunv(prog, {"hello-world"}, {envs = {XMAKE_GLOBALDIR = gd}}) - assert(out:find("repo%-ok", 1, true)) + + os.setenv("XMAKE_GLOBALDIR", gd) + os.setenv("XMAKE_MAIN_REPO", ld) + os.exec("xmake hello-world") + + os.setenv("XMAKE_MAIN_REPO", "") + os.exec("xmake hello-world") + + -- restore env vars + if prev_globaldir then + os.setenv("XMAKE_GLOBALDIR", prev_globaldir) + else + os.setenv("XMAKE_GLOBALDIR", "") + end + if prev_main_repo then + os.setenv("XMAKE_MAIN_REPO", prev_main_repo) + else + os.setenv("XMAKE_MAIN_REPO", "") + end os.tryrm(gd) os.tryrm(md) -- cgit v1.3.1 From 0376ff7ab486d773458886982f465897ca18a978 Mon Sep 17 00:00:00 2001 From: Saikari Date: Mon, 27 Jul 2026 11:51:12 +0300 Subject: Enhance plugin management by adding support for installing plugins from GitHub and local directories, and streamline repository handling in the task system. --- tests/plugins/repository/test.lua | 136 ++++++++++++---------- xmake/core/base/task.lua | 21 +--- xmake/plugins/plugin/main.lua | 229 ++++++++++++++++++++++++++------------ xmake/plugins/plugin/xmake.lua | 4 + 4 files changed, 245 insertions(+), 145 deletions(-) (limited to 'tests/plugins') diff --git a/tests/plugins/repository/test.lua b/tests/plugins/repository/test.lua index 07d11eb37..bbff847a5 100644 --- a/tests/plugins/repository/test.lua +++ b/tests/plugins/repository/test.lua @@ -1,70 +1,90 @@ -function main() - -- backup existing env vars - local prev_globaldir = os.getenv("XMAKE_GLOBALDIR") - local prev_main_repo = os.getenv("XMAKE_MAIN_REPO") - - local gd = os.tmpfile() .. ".gd" - io.writefile(gd .. "/.xmake/repositories/xmake-repo/plugins/hello-world/xmake.lua", [[ -task("hello-world") +function _write_plugin(dir, name, text) + io.writefile(path.join(dir, "xmake.lua"), string.format([[ +task("%s") set_category("plugin") on_run("main") - set_menu {usage = "xmake hello-world"} -]]) - io.writefile(gd .. "/.xmake/repositories/xmake-repo/plugins/hello-world/main.lua", [[ -function main() print("repo-ok") end -]]) + set_menu {usage = "xmake %s"} +]], name, name)) + io.writefile(path.join(dir, "main.lua"), string.format([[function main() print("%s") end]], text)) +end - os.setenv("XMAKE_GLOBALDIR", gd) - os.exec("xmake hello-world") +function main() + local global = import("core.base.global") + local suffix = path.filename(os.tmpfile()):gsub("[^%w]", "") + local reponame = "plugin-test-repository-" .. suffix + local hello_name = "plugin-test-hello-" .. suffix + local formatter_name = "plugin-test-formatter-" .. suffix + local available_name = "plugin-test-available-" .. suffix + local local_name = "plugin-test-local-" .. suffix + local repodir = os.tmpfile() .. ".plugins-repository" + local localdir = path.join(os.tmpfile() .. ".local-plugin", local_name) + local plugindir = path.join(global.directory(), "plugins") + local cachefile = path.join(global.directory(), "cache", "repository") + local cachebackup = os.tmpfile() .. ".repository" - os.exec("xmake plugin --install hello-world") + if os.isfile(cachefile) then + os.cp(cachefile, cachebackup) + end - local md = os.tmpfile() .. ".md" - io.writefile(md .. "/.xmake/plugins/manual-plugin/xmake.lua", [[ -task("manual-plugin") - set_category("plugin") - on_run("main") - set_menu {usage = "xmake manual-plugin"} -]]) - io.writefile(md .. "/.xmake/plugins/manual-plugin/main.lua", [[ -function main() print("manual") end -]]) + try + { + function () + -- mock repository with installed and available plugins + _write_plugin(path.join(repodir, "plugins", hello_name), hello_name, "repo-ok") + _write_plugin(path.join(repodir, "plugins", formatter_name), formatter_name, "format-ok") + _write_plugin(path.join(repodir, "plugins", available_name), available_name, "available-ok") + local cache = io.load(cachefile) or {} + cache.repositories = cache.repositories or {} + cache.repositories[reponame] = {repodir} + io.save(cachefile, cache) - os.setenv("XMAKE_GLOBALDIR", md) - os.exec("xmake plugin --list") - os.exec("xmake plugin --remove manual-plugin") + -- Feature: install by plain name from repository + os.exec("xmake plugin --install " .. hello_name) + os.exec("xmake " .. hello_name) - local ld = os.tmpfile() .. ".ld" - io.writefile(ld .. "/plugins/hello-world/xmake.lua", [[ -task("hello-world") - set_category("plugin") - on_run("main") - set_menu {usage = "xmake hello-world"} -]]) - io.writefile(ld .. "/plugins/hello-world/main.lua", [[ -function main() print("local-ok") end -]]) + -- Feature: install by repo@name format + os.exec("xmake plugin --install " .. reponame .. "@" .. formatter_name) + os.exec("xmake " .. formatter_name) - os.setenv("XMAKE_GLOBALDIR", gd) - os.setenv("XMAKE_MAIN_REPO", ld) - os.exec("xmake hello-world") + -- Feature: --list shows installed and available repository plugins + local out = os.iorun("xmake plugin --list") + assert(out:find(hello_name, 1, true)) + assert(out:find(formatter_name, 1, true)) + assert(out:find(available_name, 1, true)) + assert(out:find("xmake plugin --install " .. available_name, 1, true)) - os.setenv("XMAKE_MAIN_REPO", "") - os.exec("xmake hello-world") + -- Feature: install from local directory + _write_plugin(localdir, local_name, "local-ok") + os.exec("xmake plugin --install " .. os.args(localdir)) + os.exec("xmake " .. local_name) - -- restore env vars - if prev_globaldir then - os.setenv("XMAKE_GLOBALDIR", prev_globaldir) - else - os.setenv("XMAKE_GLOBALDIR", "") - end - if prev_main_repo then - os.setenv("XMAKE_MAIN_REPO", prev_main_repo) - else - os.setenv("XMAKE_MAIN_REPO", "") - end + out = os.iorun("xmake plugin --list") + assert(out:find(local_name, 1, true)) + + -- Feature: remove plugin + os.exec("xmake plugin --remove " .. local_name) + out = os.iorun("xmake plugin --list") + assert(not out:find(local_name, 1, true)) - os.tryrm(gd) - os.tryrm(md) - os.tryrm(ld) + -- Feature: install non-existent plugin fails gracefully + local ok = try { function () os.exec("xmake plugin --install plugin-test-missing-" .. suffix) end } + assert(not ok) + end, + finally + { + function () + for _, name in ipairs({hello_name, formatter_name, available_name, local_name}) do + os.tryrm(path.join(plugindir, name)) + end + if os.isfile(cachebackup) then + os.cp(cachebackup, cachefile) + else + os.tryrm(cachefile) + end + os.tryrm(cachebackup) + os.tryrm(repodir) + os.tryrm(path.directory(localdir)) + end + } + } end diff --git a/xmake/core/base/task.lua b/xmake/core/base/task.lua index 15258f8cc..a51b1dbb5 100644 --- a/xmake/core/base/task.lua +++ b/xmake/core/base/task.lua @@ -81,24 +81,9 @@ end -- the directories of tasks function task._directories() - local dirs = {path.join(global.directory(), "plugins")} - -- plugins from repositories cloned by `xrepo update-repo` - local reposdir = path.join(global.directory(), "repositories") - for _, dir in ipairs(os.dirs(path.join(reposdir, "*")) or {}) do - local plugindir = path.join(dir, "plugins") - if os.isdir(plugindir) then - table.insert(dirs, plugindir) - end - end - -- local checkout override (XMAKE_MAIN_REPO=/path/to/xmake-repo). - -- placed after the scanned repos so it takes precedence (table.join2 overwrites). - local repodir = os.getenv("XMAKE_MAIN_REPO") - if repodir and os.isdir(repodir) then - table.insert(dirs, path.join(repodir, "plugins")) - end - table.insert(dirs, path.join(os.programdir(), "plugins")) - table.insert(dirs, path.join(os.programdir(), "actions")) - return dirs + return {path.join(global.directory(), "plugins"), + path.join(os.programdir(), "plugins"), + path.join(os.programdir(), "actions")} end -- translate menu diff --git a/xmake/plugins/plugin/main.lua b/xmake/plugins/plugin/main.lua index e1525d51c..21c4b5267 100644 --- a/xmake/plugins/plugin/main.lua +++ b/xmake/plugins/plugin/main.lua @@ -21,11 +21,23 @@ -- imports import("core.base.option") import("core.base.global") +import("core.package.repository") import("devel.git") import("net.fasturl") import("private.action.require.impl.environment") --- get plugin urls +-- get plugin directory in ~/.xmake/plugins +function _get_plugindir(name) + local plugindir = path.join(global.directory(), "plugins") + return name and path.join(plugindir, name) or plugindir +end + +-- get local and global repositories, with local taking precedence +function _repositories() + return table.join(repository.repositories({global = false}), repository.repositories({global = true})) +end + +-- get plugin urls for batch install function _plugin_urls() local urls = option.get("plugins") if urls then @@ -45,9 +57,8 @@ function _plugin_urls() return urls end --- get manifest path function _manifest_path() - return path.join(global.directory(), "plugins", "manifest.txt") + return path.join(_get_plugindir(), "manifest.txt") end -- load manifest @@ -62,20 +73,87 @@ end function _save_manifest(manifest) io.save(_manifest_path(), manifest) end --- install the plugin by name from the repository. --- Repository plugins are loaded directly from the checkout; --- ensure the repository is up to date with `xrepo update-repo`. -function _install_name(name) - print("plugin %s will be loaded from the repository after xrepo update-repo.", name) + +-- find a plugin directory in the given repository directory +function _find_plugin_in_repo(repodir, name) + local dir = path.join(repodir, "plugins", name) + if os.isdir(dir) and os.isfile(path.join(dir, "xmake.lua")) then + return dir + end end --- install plugins -function _install() - -- install the plugin by name from the repository? - local name = option.get("plugins") - if name and not os.isdir(name) and not name:find("[/\\:]") then - return _install_name(name) +-- install a plugin from the given repository or the first repository containing it +function _install_plugins_from_repo(name, reponame) + for _, repo in ipairs(_repositories()) do + if not reponame or repo:name() == reponame then + local srcdir = _find_plugin_in_repo(repo:directory(), name) + if srcdir then + local dstdir = _get_plugindir(name) + assert(not os.isdir(dstdir), "plugin(%s) already exists!", name) + os.vcp(srcdir, dstdir) + cprint("${color.success}install ${bright}%s${clear} from repository ${bright}%s${clear} ok!", name, repo:name()) + return + end + end end + if reponame then + raise("plugin(%s): not found in repository %s!", name, reponame) + end + raise("plugin(%s): not found in any repository! try ${bright}xrepo update-repo${clear} first.", name) +end + +-- install a plugin from a local directory +function _install_from_local(dir) + assert(os.isdir(dir) and os.isfile(path.join(dir, "xmake.lua")), "plugin path(%s): ${bright}xmake.lua${clear} not found!", dir) + local name = path.filename(path.absolute(dir)) + local dstdir = _get_plugindir(name) + assert(not os.isdir(dstdir), "plugin(%s) already exists!", name) + os.vcp(dir, dstdir) + cprint("${color.success}install ${bright}%s${clear} from ${bright}%s${clear} ok!", name, dir) +end + +-- install a plugin from a git url or github shortcut +function _install_from_git(url) + local branch + if url:startswith("github:") then + url = url:sub(8) + local i = url:find("#", 1, true) + if i then + branch = url:sub(i + 1) + url = url:sub(1, i - 1) + end + url = "https://github.com/" .. url .. ".git" + end + local tmpdir = os.tmpfile() .. ".dir" + local clone_opt = {verbose = option.get("verbose"), outputdir = tmpdir} + if branch then + clone_opt.branch = branch + end + git.clone(git.asgiturl(url) or url, clone_opt) + local found = false + local function install(srcdir, name) + local dstdir = _get_plugindir(name) + assert(not os.isdir(dstdir), "plugin(%s) already exists!", name) + os.vcp(srcdir, dstdir) + cprint(" ${color.success}-> ${bright}%s${clear}", name) + found = true + end + if os.isfile(path.join(tmpdir, "xmake.lua")) then + install(tmpdir, path.basename(path.filename(url))) + else + for _, filepath in ipairs(os.files(path.join(tmpdir, "*", "xmake.lua"))) do + local srcdir = path.directory(filepath) + install(srcdir, path.filename(srcdir)) + end + end + os.tryrm(tmpdir) + if not found then + raise("no plugin found in %s", url) + end +end + +-- install plugins +function _install() -- enter environment environment.enter() @@ -85,12 +163,45 @@ function _install() function () -- do install + local name = option.get("plugins") + if name and #name > 0 then + -- parse repo@plugin format + local i = name:find("@", 1, true) + if i and not name:find("[/\\:]") then + local reponame = name:sub(1, i - 1) + local pluginname = name:sub(i + 1) + _install_plugins_from_repo(pluginname, reponame) + return + end + + -- github shortcut: github:user/repo or github:user/repo#branch + if name:startswith("github:") then + _install_from_git(name) + return + end + + -- git url or local path + if os.isdir(name) or name:find("[/\\:]") then + if os.isdir(name) then + _install_from_local(name) + else + _install_from_git(name) + end + return + end + + -- plain name: try to find it in repositories + _install_plugins_from_repo(name) + return + end + + -- do batch install from plugin collection urls local urls = _plugin_urls() local tmpdir = os.tmpfile() .. ".dir" - local plugindir = path.join(global.directory(), "plugins") + local plugindir = _get_plugindir() local installed_url for _, url in ipairs(urls) do - cprint("installing plugins from %s ..", url) + cprint("installing plugins from ${bright}%s${clear} ..", url) git.clone(url, {verbose = option.get("verbose"), outputdir = tmpdir}) installed_url = url break @@ -98,21 +209,20 @@ function _install() for _, filepath in ipairs(os.files(path.join(tmpdir, "*", "xmake.lua"))) do local srcdir = path.directory(filepath) local name = path.filename(srcdir) - local dstdir = path.join(plugindir, name) + local dstdir = _get_plugindir(name) assert(not os.isdir(dstdir), "plugin(%s) already exists!", name) os.vcp(srcdir, dstdir) - cprint(" ${yellow}->${clear} %s", name) + cprint(" ${color.success}-> ${bright}%s${clear}", name) end os.tryrm(tmpdir) - -- save manifest if installed_url then local manifest = _load_manifest() or {} manifest.urls = manifest.urls or {} table.join2(manifest.urls, installed_url) _save_manifest(manifest) end - cprint("${bright}all plugins have been installed in %s!", plugindir) + cprint("${color.success}all plugins have been installed in ${bright}%s${clear}!", plugindir) end, catch { @@ -140,22 +250,22 @@ function _update() local manifest = _load_manifest() assert(manifest and manifest.urls, "3rd plugins not found!") local urls = manifest.urls - local plugindir = path.join(global.directory(), "plugins") + local plugindir = _get_plugindir() for _, url in ipairs(urls) do - cprint("updating plugins from %s ..", url) + cprint("updating plugins from ${bright}%s${clear} ..", url) local tmpdir = os.tmpfile() .. ".dir" git.clone(url, {verbose = option.get("verbose"), outputdir = tmpdir}) for _, filepath in ipairs(os.files(path.join(tmpdir, "*", "xmake.lua"))) do local srcdir = path.directory(filepath) local name = path.filename(srcdir) - local dstdir = path.join(plugindir, name) + local dstdir = _get_plugindir(name) os.tryrm(dstdir) os.vcp(srcdir, dstdir) - cprint(" ${yellow}->${clear} %s", name) + cprint(" ${color.success}-> ${bright}%s${clear}", name) end os.tryrm(tmpdir) end - cprint("${bright}all plugins have been updated in %s!", plugindir) + cprint("${color.success}all plugins have been updated in ${bright}%s${clear}!", plugindir) end, catch { @@ -169,80 +279,61 @@ function _update() environment.leave() end --- remove the given installed plugin (from ~/.xmake/plugins/) +-- remove the given installed plugin function _remove() local name = assert(option.get("plugins"), "please specify the plugin name to be removed!") assert(name ~= "" and name ~= "." and not name:find("..", 1, true) and not name:find("[/\\:]"), "invalid plugin name(%s)!", name) - local plugindir = path.join(global.directory(), "plugins", name) - assert(os.isdir(plugindir), "plugin(%s) not found!", name) - os.rmdir(plugindir) - cprint("${color.success}remove plugin(%s) ok!", name) + local dir = _get_plugindir(name) + assert(os.isdir(dir), "plugin(%s) not found!", name) + os.rmdir(dir) + cprint("${color.success}remove ${bright}%s${clear} ok!", name) end --- list all installed plugins (manual + repository) +-- list all plugins function _list() local seen = {} - -- manually installed plugins - local plugindir = path.join(global.directory(), "plugins") - cprint("plugins in ${bright}%s${clear}:", plugindir) + + -- installed plugins + local plugindir = _get_plugindir() + cprint("${bright}the installed plugins:${clear}") local found = false for _, dir in ipairs(os.dirs(path.join(plugindir, "*")) or {}) do if os.isfile(path.join(dir, "xmake.lua")) then local name = path.filename(dir) seen[name] = true found = true - cprint(" ${color.dump.string}%s${clear}", name) + cprint(" ${bright}%s${clear}", name) end end if not found then print(" (none)") end - -- repository plugins (from scanned repos) - local reposdir = path.join(global.directory(), "repositories") - for _, dir in ipairs(os.dirs(path.join(reposdir, "*")) or {}) do - local rplugindir = path.join(dir, "plugins") + -- plugins available in repositories (not yet installed) + cprint("${bright}in xmake-repo:${clear}") + local avail = false + local repos = _repositories() + for _, repo in ipairs(repos) do + local rplugindir = path.join(repo:directory(), "plugins") if os.isdir(rplugindir) then - local reponame = path.filename(dir) - cprint("plugins in repository ${bright}%s${clear}:", reponame) - local repofound = false for _, subdir in ipairs(os.dirs(path.join(rplugindir, "*")) or {}) do - if os.isfile(path.join(subdir, "xmake.lua")) and not seen[path.filename(subdir)] then - seen[path.filename(subdir)] = true - repofound = true - cprint(" ${color.dump.string}%s${clear}", path.filename(subdir)) + local name = path.filename(subdir) + if os.isfile(path.join(subdir, "xmake.lua")) and not seen[name] then + seen[name] = true + avail = true + cprint(" - ${bright}%s${clear} ${dim}(run ${bright}xmake plugin --install %s${clear}${dim} to install)${clear}", name, name) end end - if not repofound then - print(" (none)") - end end end - - -- local checkout plugins - local repodir = os.getenv("XMAKE_MAIN_REPO") - if repodir and os.isdir(repodir) then - local rplugindir = path.join(repodir, "plugins") - if os.isdir(rplugindir) then - cprint("plugins in ${bright}XMAKE_MAIN_REPO${clear}:") - local repofound = false - for _, subdir in ipairs(os.dirs(path.join(rplugindir, "*")) or {}) do - if os.isfile(path.join(subdir, "xmake.lua")) and not seen[path.filename(subdir)] then - seen[path.filename(subdir)] = true - repofound = true - cprint(" ${color.dump.string}%s${clear}", path.filename(subdir)) - end - end - if not repofound then - print(" (none)") - end - end + if not avail then + print(" (none)") end end -- clear all installed plugins function _clear() - local plugindir = path.join(global.directory(), "plugins") + local plugindir = _get_plugindir() if os.isdir(plugindir) then os.rmdir(plugindir) end diff --git a/xmake/plugins/plugin/xmake.lua b/xmake/plugins/plugin/xmake.lua index 87d85e81d..a1b7f61ba 100644 --- a/xmake/plugins/plugin/xmake.lua +++ b/xmake/plugins/plugin/xmake.lua @@ -33,6 +33,10 @@ task("plugin") {nil, "plugins", "v", nil, "The plugins path, url or package name.", "e.g.", " $ xmake plugin --install https://github.com/xmake-io/xmake-plugins", + " $ xmake plugin --install github:xmake-io/xmake-plugins", + " $ xmake plugin --install github:xmake-io/xmake-plugins#dev", + " $ xmake plugin --install /tmp/my-plugin", + " $ xmake plugin --install xmake-repo@hello-world", " $ xmake plugin --install hello-world", " $ xmake plugin --remove hello-world", " $ xmake plugin --list", -- cgit v1.3.1 From 235b82edd8b262a7080a9f9ee518567f5a54b836 Mon Sep 17 00:00:00 2001 From: Saikari Date: Mon, 27 Jul 2026 12:36:07 +0300 Subject: Enhance plugin installation by validating plugin names and improving error handling during installation and updates --- tests/plugins/repository/test.lua | 50 ++++--- xmake/plugins/plugin/main.lua | 266 ++++++++++++++++++++++---------------- xmake/plugins/plugin/xmake.lua | 2 +- 3 files changed, 188 insertions(+), 130 deletions(-) (limited to 'tests/plugins') diff --git a/tests/plugins/repository/test.lua b/tests/plugins/repository/test.lua index bbff847a5..e0c67058b 100644 --- a/tests/plugins/repository/test.lua +++ b/tests/plugins/repository/test.lua @@ -19,13 +19,28 @@ function main() local repodir = os.tmpfile() .. ".plugins-repository" local localdir = path.join(os.tmpfile() .. ".local-plugin", local_name) local plugindir = path.join(global.directory(), "plugins") - local cachefile = path.join(global.directory(), "cache", "repository") + local cachefile = path.join(global.cachedir(), "repository") local cachebackup = os.tmpfile() .. ".repository" if os.isfile(cachefile) then os.cp(cachefile, cachebackup) end + local function cleanup() + for _, name in ipairs({hello_name, formatter_name, available_name, local_name}) do + os.tryrm(path.join(plugindir, name)) + end + if os.isfile(cachebackup) then + os.cp(cachebackup, cachefile) + else + os.tryrm(cachefile) + end + os.tryrm(cachebackup) + os.tryrm(repodir) + os.tryrm(path.directory(localdir)) + end + + try { function () @@ -33,17 +48,15 @@ function main() _write_plugin(path.join(repodir, "plugins", hello_name), hello_name, "repo-ok") _write_plugin(path.join(repodir, "plugins", formatter_name), formatter_name, "format-ok") _write_plugin(path.join(repodir, "plugins", available_name), available_name, "available-ok") - local cache = io.load(cachefile) or {} + os.mkdir(path.directory(cachefile)) + local cache = os.isfile(cachefile) and io.load(cachefile) or {} cache.repositories = cache.repositories or {} cache.repositories[reponame] = {repodir} io.save(cachefile, cache) - -- Feature: install by plain name from repository - os.exec("xmake plugin --install " .. hello_name) + -- Feature: install by plain name and repo@name in one invocation + os.exec("xmake plugin --install " .. hello_name .. " " .. reponame .. "@" .. formatter_name) os.exec("xmake " .. hello_name) - - -- Feature: install by repo@name format - os.exec("xmake plugin --install " .. reponame .. "@" .. formatter_name) os.exec("xmake " .. formatter_name) -- Feature: --list shows installed and available repository plugins @@ -69,21 +82,22 @@ function main() -- Feature: install non-existent plugin fails gracefully local ok = try { function () os.exec("xmake plugin --install plugin-test-missing-" .. suffix) end } assert(not ok) + -- Feature: reject plugin name traversal + ok = try { function () os.exec("xmake plugin --install " .. reponame .. "@..") end } + assert(not ok) + end, + catch + { + function (errors) + cleanup() + raise(errors) + end + }, finally { function () - for _, name in ipairs({hello_name, formatter_name, available_name, local_name}) do - os.tryrm(path.join(plugindir, name)) - end - if os.isfile(cachebackup) then - os.cp(cachebackup, cachefile) - else - os.tryrm(cachefile) - end - os.tryrm(cachebackup) - os.tryrm(repodir) - os.tryrm(path.directory(localdir)) + cleanup() end } } diff --git a/xmake/plugins/plugin/main.lua b/xmake/plugins/plugin/main.lua index a29ad8687..db03c7abb 100644 --- a/xmake/plugins/plugin/main.lua +++ b/xmake/plugins/plugin/main.lua @@ -26,10 +26,16 @@ import("devel.git") import("net.fasturl") import("private.action.require.impl.environment") +-- validate a plugin directory name +function _check_plugin_name(name) + assert(type(name) == "string" and name ~= "" and name ~= "." and not name:find("..", 1, true) and not name:find("[/\\:]"), "invalid plugin name(%s)!", name) + return name +end + -- get plugin directory in ~/.xmake/plugins function _get_plugindir(name) local plugindir = path.join(global.directory(), "plugins") - return name and path.join(plugindir, name) or plugindir + return name and path.join(plugindir, _check_plugin_name(name)) or plugindir end -- get local and global repositories, with local taking precedence @@ -39,22 +45,36 @@ end -- get plugin urls for batch install function _plugin_urls() - local urls = option.get("plugins") - if urls then - local result = {} - for _, url in ipairs(urls) do - table.insert(result, git.asgiturl(url) or url) - end - urls = result - else - urls = { - "https://github.com/xmake-io/xmake-plugins.git", - "https://gitlab.com/tboox/xmake-plugins.git", - "https://gitee.com/tboox/xmake-plugins.git"} - urls = fasturl.add(urls) - urls = fasturl.sort(urls) - end - return urls + local urls = { + "https://github.com/xmake-io/xmake-plugins.git", + "https://gitlab.com/tboox/xmake-plugins.git", + "https://gitee.com/tboox/xmake-plugins.git"} + fasturl.add(urls) + return fasturl.sort(urls) +end + +-- run a function with a temporary directory that is removed on success or failure +function _with_tmpdir(fn) + local tmpdir = os.tmpfile() .. ".dir" + return try + { + function () + return fn(tmpdir) + end, + catch + { + function (errors) + os.tryrm(tmpdir) + raise(errors) + end + }, + finally + { + function () + os.tryrm(tmpdir) + end + } + } end function _manifest_path() @@ -84,6 +104,7 @@ end -- install a plugin from the given repository or the first repository containing it function _install_plugins_from_repo(name, reponame) + _check_plugin_name(name) for _, repo in ipairs(_repositories()) do if not reponame or repo:name() == reponame then local srcdir = _find_plugin_in_repo(repo:directory(), name) @@ -124,32 +145,65 @@ function _install_from_git(url) end url = "https://github.com/" .. url .. ".git" end - local tmpdir = os.tmpfile() .. ".dir" - local clone_opt = {verbose = option.get("verbose"), outputdir = tmpdir} - if branch then - clone_opt.branch = branch - end - git.clone(git.asgiturl(url) or url, clone_opt) - local found = false - local function install(srcdir, name) - local dstdir = _get_plugindir(name) - assert(not os.isdir(dstdir), "plugin(%s) already exists!", name) - os.vcp(srcdir, dstdir) - cprint(" ${color.success}-> ${bright}%s${clear}", name) - found = true - end - if os.isfile(path.join(tmpdir, "xmake.lua")) then - install(tmpdir, path.basename(path.filename(url))) - else - for _, filepath in ipairs(os.files(path.join(tmpdir, "*", "xmake.lua"))) do - local srcdir = path.directory(filepath) - install(srcdir, path.filename(srcdir)) + _with_tmpdir(function (tmpdir) + local clone_opt = {verbose = option.get("verbose"), outputdir = tmpdir} + if branch then + clone_opt.branch = branch + end + git.clone(git.asgiturl(url) or url, clone_opt) + local found = false + local function install(srcdir, name) + local dstdir = _get_plugindir(name) + assert(not os.isdir(dstdir), "plugin(%s) already exists!", name) + os.vcp(srcdir, dstdir) + cprint(" ${color.success}-> ${bright}%s${clear}", name) + found = true end + if os.isfile(path.join(tmpdir, "xmake.lua")) then + install(tmpdir, path.basename(path.filename(url))) + else + for _, filepath in ipairs(os.files(path.join(tmpdir, "*", "xmake.lua"))) do + local srcdir = path.directory(filepath) + install(srcdir, path.filename(srcdir)) + end + end + if not found then + raise("no plugin found in %s", url) + end + end) +end + +-- install a single plugin +function _install_one(name) + -- parse repo@plugin format + local i = name:find("@", 1, true) + if i and not name:find("[/\\:]") then + local reponame = name:sub(1, i - 1) + local pluginname = name:sub(i + 1) + _install_plugins_from_repo(pluginname, reponame) + return end - os.tryrm(tmpdir) - if not found then - raise("no plugin found in %s", url) + + -- github shortcut: github:user/repo or github:user/repo#branch + if name:startswith("github:") then + _install_from_git(name) + return + end + + -- git url or local path + if name:startswith("file://") or git.asgiturl(name) then + _install_from_git(name) + return + elseif os.isdir(name) then + _install_from_local(name) + return + elseif name:find("[/\\:]") then + _install_from_git(name) + return end + + -- plain name: try to find it in repositories + _install_plugins_from_repo(name) end -- install plugins @@ -158,84 +212,69 @@ function _install() -- enter environment environment.enter() + local errors try { function () - -- do install - local name = option.get("plugins") - if name and #name > 0 then - -- parse repo@plugin format - local i = name:find("@", 1, true) - if i and not name:find("[/\\:]") then - local reponame = name:sub(1, i - 1) - local pluginname = name:sub(i + 1) - _install_plugins_from_repo(pluginname, reponame) - return - end - - -- github shortcut: github:user/repo or github:user/repo#branch - if name:startswith("github:") then - _install_from_git(name) - return - end - - -- git url or local path - if name:startswith("file://") or git.asgiturl(name) then - _install_from_git(name) - return - elseif os.isdir(name) then - _install_from_local(name) - return - elseif name:find("[/\\:]") then - _install_from_git(name) - return + -- install requested plugins + local names = option.get("plugins") + if names then + for _, name in ipairs(names) do + _install_one(name) end - - -- plain name: try to find it in repositories - _install_plugins_from_repo(name) return end -- do batch install from plugin collection urls local urls = _plugin_urls() - local tmpdir = os.tmpfile() .. ".dir" local plugindir = _get_plugindir() local installed_url - for _, url in ipairs(urls) do - cprint("installing plugins from ${bright}%s${clear} ..", url) - git.clone(url, {verbose = option.get("verbose"), outputdir = tmpdir}) - installed_url = url - break - end - for _, filepath in ipairs(os.files(path.join(tmpdir, "*", "xmake.lua"))) do - local srcdir = path.directory(filepath) - local name = path.filename(srcdir) - local dstdir = _get_plugindir(name) - assert(not os.isdir(dstdir), "plugin(%s) already exists!", name) - os.vcp(srcdir, dstdir) - cprint(" ${color.success}-> ${bright}%s${clear}", name) - end - os.tryrm(tmpdir) - - if installed_url then - local manifest = _load_manifest() or {} - manifest.urls = manifest.urls or {} - table.join2(manifest.urls, installed_url) - _save_manifest(manifest) - end + _with_tmpdir(function (tmpdir) + for _, url in ipairs(urls) do + cprint("installing plugins from ${bright}%s${clear} ..", url) + local ok = try + { + function () + git.clone(url, {verbose = option.get("verbose"), outputdir = tmpdir}) + return true + end + } + if ok then + installed_url = url + break + end + os.tryrm(tmpdir) + end + assert(installed_url, "failed to install plugins from all urls!") + for _, filepath in ipairs(os.files(path.join(tmpdir, "*", "xmake.lua"))) do + local srcdir = path.directory(filepath) + local name = path.filename(srcdir) + local dstdir = _get_plugindir(name) + assert(not os.isdir(dstdir), "plugin(%s) already exists!", name) + os.vcp(srcdir, dstdir) + cprint(" ${color.success}-> ${bright}%s${clear}", name) + end + end) + local manifest = _load_manifest() or {} + manifest.urls = manifest.urls or {} + table.join2(manifest.urls, installed_url) + _save_manifest(manifest) cprint("${color.success}all plugins have been installed in ${bright}%s${clear}!", plugindir) end, catch { - function (errors) - raise(errors) + function (_errors) + errors = _errors end } } -- leave environment environment.leave() + if errors then + raise(errors) + end end -- update plugins @@ -244,6 +283,7 @@ function _update() -- enter environment environment.enter() + local errors try { function () @@ -255,36 +295,40 @@ function _update() local plugindir = _get_plugindir() for _, url in ipairs(urls) do cprint("updating plugins from ${bright}%s${clear} ..", url) - local tmpdir = os.tmpfile() .. ".dir" - git.clone(url, {verbose = option.get("verbose"), outputdir = tmpdir}) - for _, filepath in ipairs(os.files(path.join(tmpdir, "*", "xmake.lua"))) do - local srcdir = path.directory(filepath) - local name = path.filename(srcdir) - local dstdir = _get_plugindir(name) - os.tryrm(dstdir) - os.vcp(srcdir, dstdir) - cprint(" ${color.success}-> ${bright}%s${clear}", name) - end - os.tryrm(tmpdir) + _with_tmpdir(function (tmpdir) + git.clone(url, {verbose = option.get("verbose"), outputdir = tmpdir}) + for _, filepath in ipairs(os.files(path.join(tmpdir, "*", "xmake.lua"))) do + local srcdir = path.directory(filepath) + local name = path.filename(srcdir) + local dstdir = _get_plugindir(name) + os.tryrm(dstdir) + os.vcp(srcdir, dstdir) + cprint(" ${color.success}-> ${bright}%s${clear}", name) + end + end) end cprint("${color.success}all plugins have been updated in ${bright}%s${clear}!", plugindir) end, catch { - function (errors) - raise(errors) + function (_errors) + errors = _errors end } } -- leave environment environment.leave() + if errors then + raise(errors) + end end -- remove the given installed plugin function _remove() - local name = assert(option.get("plugins"), "please specify the plugin name to be removed!") - assert(name ~= "" and name ~= "." and not name:find("..", 1, true) and not name:find("[/\\:]"), "invalid plugin name(%s)!", name) + local names = assert(option.get("plugins"), "please specify the plugin name to be removed!") + assert(#names == 1, "please specify only one plugin name to be removed!") + local name = _check_plugin_name(names[1]) local dir = _get_plugindir(name) assert(os.isdir(dir), "plugin(%s) not found!", name) os.rmdir(dir) diff --git a/xmake/plugins/plugin/xmake.lua b/xmake/plugins/plugin/xmake.lua index a1b7f61ba..aca6def8d 100644 --- a/xmake/plugins/plugin/xmake.lua +++ b/xmake/plugins/plugin/xmake.lua @@ -30,7 +30,7 @@ task("plugin") {'r', "remove", "k", nil, "Remove the given installed plugin."}, {'l', "list", "k", nil, "List all installed plugins."}, {'c', "clear", "k", nil, "Clear all installed plugins."}, - {nil, "plugins", "v", nil, "The plugins path, url or package name.", + {nil, "plugins", "vs", nil, "The plugin paths, urls or names.", "e.g.", " $ xmake plugin --install https://github.com/xmake-io/xmake-plugins", " $ xmake plugin --install github:xmake-io/xmake-plugins", -- cgit v1.3.1 From 2127d0cef9be4eeab3e0192ab9bcd7aa36f9d658 Mon Sep 17 00:00:00 2001 From: Saikari Date: Tue, 28 Jul 2026 00:04:16 +0300 Subject: Refactor plugin installation logic to support custom plugin URLs and improve error handling during installation --- tests/plugins/repository/test.lua | 114 +++++++---------- xmake/plugins/plugin/main.lua | 259 ++++++++++++++++---------------------- xmake/plugins/plugin/xmake.lua | 2 +- 3 files changed, 160 insertions(+), 215 deletions(-) (limited to 'tests/plugins') diff --git a/tests/plugins/repository/test.lua b/tests/plugins/repository/test.lua index e0c67058b..c4c6c4c20 100644 --- a/tests/plugins/repository/test.lua +++ b/tests/plugins/repository/test.lua @@ -1,3 +1,5 @@ +local global = import("core.base.global") + function _write_plugin(dir, name, text) io.writefile(path.join(dir, "xmake.lua"), string.format([[ task("%s") @@ -8,8 +10,7 @@ task("%s") io.writefile(path.join(dir, "main.lua"), string.format([[function main() print("%s") end]], text)) end -function main() - local global = import("core.base.global") +function main(t) local suffix = path.filename(os.tmpfile()):gsub("[^%w]", "") local reponame = "plugin-test-repository-" .. suffix local hello_name = "plugin-test-hello-" .. suffix @@ -20,85 +21,66 @@ function main() local localdir = path.join(os.tmpfile() .. ".local-plugin", local_name) local plugindir = path.join(global.directory(), "plugins") local cachefile = path.join(global.cachedir(), "repository") - local cachebackup = os.tmpfile() .. ".repository" - - if os.isfile(cachefile) then - os.cp(cachefile, cachebackup) - end + -- reset test state local function cleanup() for _, name in ipairs({hello_name, formatter_name, available_name, local_name}) do os.tryrm(path.join(plugindir, name)) end - if os.isfile(cachebackup) then - os.cp(cachebackup, cachefile) - else - os.tryrm(cachefile) - end - os.tryrm(cachebackup) + local cache = os.isfile(cachefile) and io.load(cachefile) or {} + cache.repositories = cache.repositories or {} + cache.repositories[reponame] = nil + io.save(cachefile, cache) os.tryrm(repodir) os.tryrm(path.directory(localdir)) end + cleanup() + + -- mock repository with installed and available plugins + _write_plugin(path.join(repodir, "plugins", hello_name), hello_name, "repo-ok") + _write_plugin(path.join(repodir, "plugins", formatter_name), formatter_name, "format-ok") + _write_plugin(path.join(repodir, "plugins", available_name), available_name, "available-ok") + os.mkdir(path.directory(cachefile)) + local cache = os.isfile(cachefile) and io.load(cachefile) or {} + cache.repositories = cache.repositories or {} + cache.repositories[reponame] = {repodir} + io.save(cachefile, cache) + -- Feature: install by plain name from repository + os.runv("xmake", {"plugin", "--install", hello_name}) + os.runv("xmake", {hello_name}) - try - { - function () - -- mock repository with installed and available plugins - _write_plugin(path.join(repodir, "plugins", hello_name), hello_name, "repo-ok") - _write_plugin(path.join(repodir, "plugins", formatter_name), formatter_name, "format-ok") - _write_plugin(path.join(repodir, "plugins", available_name), available_name, "available-ok") - os.mkdir(path.directory(cachefile)) - local cache = os.isfile(cachefile) and io.load(cachefile) or {} - cache.repositories = cache.repositories or {} - cache.repositories[reponame] = {repodir} - io.save(cachefile, cache) + -- Feature: install by repo@name format + os.runv("xmake", {"plugin", "--install", reponame .. "@" .. formatter_name}) + os.runv("xmake", {formatter_name}) - -- Feature: install by plain name and repo@name in one invocation - os.exec("xmake plugin --install " .. hello_name .. " " .. reponame .. "@" .. formatter_name) - os.exec("xmake " .. hello_name) - os.exec("xmake " .. formatter_name) + -- Feature: --list shows installed and available repository plugins + local out = os.iorun("xmake plugin --list") + t:require(out:find(hello_name, 1, true)) + t:require(out:find(formatter_name, 1, true)) + t:require(out:find(available_name, 1, true)) + t:require(out:find("xmake plugin --install " .. available_name, 1, true)) - -- Feature: --list shows installed and available repository plugins - local out = os.iorun("xmake plugin --list") - assert(out:find(hello_name, 1, true)) - assert(out:find(formatter_name, 1, true)) - assert(out:find(available_name, 1, true)) - assert(out:find("xmake plugin --install " .. available_name, 1, true)) + -- Feature: install from local directory + _write_plugin(localdir, local_name, "local-ok") + os.runv("xmake", {"plugin", "--install", localdir}) + os.runv("xmake", {local_name}) - -- Feature: install from local directory - _write_plugin(localdir, local_name, "local-ok") - os.exec("xmake plugin --install " .. os.args(localdir)) - os.exec("xmake " .. local_name) + out = os.iorun("xmake plugin --list") + t:require(out:find(local_name, 1, true)) - out = os.iorun("xmake plugin --list") - assert(out:find(local_name, 1, true)) + -- Feature: remove plugin + os.runv("xmake", {"plugin", "--remove", local_name}) + out = os.iorun("xmake plugin --list") + t:require_not(out:find(local_name, 1, true)) - -- Feature: remove plugin - os.exec("xmake plugin --remove " .. local_name) - out = os.iorun("xmake plugin --list") - assert(not out:find(local_name, 1, true)) + -- Feature: install non-existent plugin fails gracefully + local ok = try { function () os.runv("xmake", {"plugin", "--install", "plugin-test-missing-" .. suffix}) return true end } + t:require_not(ok) - -- Feature: install non-existent plugin fails gracefully - local ok = try { function () os.exec("xmake plugin --install plugin-test-missing-" .. suffix) end } - assert(not ok) - -- Feature: reject plugin name traversal - ok = try { function () os.exec("xmake plugin --install " .. reponame .. "@..") end } - assert(not ok) + -- Feature: reject plugin name traversal + ok = try { function () os.runv("xmake", {"plugin", "--install", reponame .. "@.."}) return true end } + t:require_not(ok) - end, - catch - { - function (errors) - cleanup() - raise(errors) - end - }, - finally - { - function () - cleanup() - end - } - } + cleanup() end diff --git a/xmake/plugins/plugin/main.lua b/xmake/plugins/plugin/main.lua index db03c7abb..09de02983 100644 --- a/xmake/plugins/plugin/main.lua +++ b/xmake/plugins/plugin/main.lua @@ -45,36 +45,22 @@ end -- get plugin urls for batch install function _plugin_urls() - local urls = { - "https://github.com/xmake-io/xmake-plugins.git", - "https://gitlab.com/tboox/xmake-plugins.git", - "https://gitee.com/tboox/xmake-plugins.git"} - fasturl.add(urls) - return fasturl.sort(urls) -end - --- run a function with a temporary directory that is removed on success or failure -function _with_tmpdir(fn) - local tmpdir = os.tmpfile() .. ".dir" - return try - { - function () - return fn(tmpdir) - end, - catch - { - function (errors) - os.tryrm(tmpdir) - raise(errors) - end - }, - finally - { - function () - os.tryrm(tmpdir) - end - } - } + local urls = option.get("plugins") + if urls then + local result = {} + for _, url in ipairs(table.wrap(urls)) do + table.insert(result, git.asgiturl(url) or url) + end + urls = result + else + urls = { + "https://github.com/xmake-io/xmake-plugins.git", + "https://gitlab.com/tboox/xmake-plugins.git", + "https://gitee.com/tboox/xmake-plugins.git"} + fasturl.add(urls) + urls = fasturl.sort(urls) + end + return urls end function _manifest_path() @@ -143,67 +129,34 @@ function _install_from_git(url) branch = url:sub(i + 1) url = url:sub(1, i - 1) end - url = "https://github.com/" .. url .. ".git" + url = git.asgiturl("github:" .. url) end - _with_tmpdir(function (tmpdir) - local clone_opt = {verbose = option.get("verbose"), outputdir = tmpdir} - if branch then - clone_opt.branch = branch - end - git.clone(git.asgiturl(url) or url, clone_opt) - local found = false - local function install(srcdir, name) - local dstdir = _get_plugindir(name) - assert(not os.isdir(dstdir), "plugin(%s) already exists!", name) - os.vcp(srcdir, dstdir) - cprint(" ${color.success}-> ${bright}%s${clear}", name) - found = true - end - if os.isfile(path.join(tmpdir, "xmake.lua")) then - install(tmpdir, path.basename(path.filename(url))) - else - for _, filepath in ipairs(os.files(path.join(tmpdir, "*", "xmake.lua"))) do - local srcdir = path.directory(filepath) - install(srcdir, path.filename(srcdir)) - end - end - if not found then - raise("no plugin found in %s", url) - end - end) -end - --- install a single plugin -function _install_one(name) - -- parse repo@plugin format - local i = name:find("@", 1, true) - if i and not name:find("[/\\:]") then - local reponame = name:sub(1, i - 1) - local pluginname = name:sub(i + 1) - _install_plugins_from_repo(pluginname, reponame) - return + local tmpdir = os.tmpfile() .. ".dir" + local clone_opt = {verbose = option.get("verbose"), outputdir = tmpdir} + if branch then + clone_opt.branch = branch end - - -- github shortcut: github:user/repo or github:user/repo#branch - if name:startswith("github:") then - _install_from_git(name) - return + git.clone(git.asgiturl(url) or url, clone_opt) + local found = false + local function install(srcdir, name) + local dstdir = _get_plugindir(name) + assert(not os.isdir(dstdir), "plugin(%s) already exists!", name) + os.vcp(srcdir, dstdir) + cprint(" ${color.success}-> ${bright}%s${clear}", name) + found = true end - - -- git url or local path - if name:startswith("file://") or git.asgiturl(name) then - _install_from_git(name) - return - elseif os.isdir(name) then - _install_from_local(name) - return - elseif name:find("[/\\:]") then - _install_from_git(name) - return + if os.isfile(path.join(tmpdir, "xmake.lua")) then + install(tmpdir, path.basename(path.filename(url))) + else + for _, filepath in ipairs(os.files(path.join(tmpdir, "*", "xmake.lua"))) do + local srcdir = path.directory(filepath) + install(srcdir, path.filename(srcdir)) + end + end + os.tryrm(tmpdir) + if not found then + raise("no plugin found in %s", url) end - - -- plain name: try to find it in repositories - _install_plugins_from_repo(name) end -- install plugins @@ -212,69 +165,84 @@ function _install() -- enter environment environment.enter() - local errors try { function () - -- install requested plugins - local names = option.get("plugins") - if names then - for _, name in ipairs(names) do - _install_one(name) + -- do install + local name = option.get("plugins") + if name and #name > 0 then + -- parse repo@plugin format + local i = name:find("@", 1, true) + if i and not name:find("[/\\:]") then + local reponame = name:sub(1, i - 1) + local pluginname = name:sub(i + 1) + _install_plugins_from_repo(pluginname, reponame) + return end + + -- github shortcut: github:user/repo or github:user/repo#branch + if name:startswith("github:") then + _install_from_git(name) + return + end + + -- git url or local path + if name:startswith("file://") or git.asgiturl(name) then + _install_from_git(name) + return + elseif os.isdir(name) then + _install_from_local(name) + return + elseif name:find("[/\\:]") then + _install_from_git(name) + return + end + + -- plain name: try to find it in repositories + _install_plugins_from_repo(name) return end -- do batch install from plugin collection urls local urls = _plugin_urls() + local tmpdir = os.tmpfile() .. ".dir" local plugindir = _get_plugindir() local installed_url - _with_tmpdir(function (tmpdir) - for _, url in ipairs(urls) do - cprint("installing plugins from ${bright}%s${clear} ..", url) - local ok = try - { - function () - git.clone(url, {verbose = option.get("verbose"), outputdir = tmpdir}) - return true - end - } - if ok then - installed_url = url - break - end - os.tryrm(tmpdir) - end - assert(installed_url, "failed to install plugins from all urls!") - for _, filepath in ipairs(os.files(path.join(tmpdir, "*", "xmake.lua"))) do - local srcdir = path.directory(filepath) - local name = path.filename(srcdir) - local dstdir = _get_plugindir(name) - assert(not os.isdir(dstdir), "plugin(%s) already exists!", name) - os.vcp(srcdir, dstdir) - cprint(" ${color.success}-> ${bright}%s${clear}", name) - end - end) - local manifest = _load_manifest() or {} - manifest.urls = manifest.urls or {} - table.join2(manifest.urls, installed_url) - _save_manifest(manifest) + for _, url in ipairs(urls) do + cprint("installing plugins from ${bright}%s${clear} ..", url) + git.clone(url, {verbose = option.get("verbose"), outputdir = tmpdir}) + installed_url = url + break + end + for _, filepath in ipairs(os.files(path.join(tmpdir, "*", "xmake.lua"))) do + local srcdir = path.directory(filepath) + local name = path.filename(srcdir) + local dstdir = _get_plugindir(name) + assert(not os.isdir(dstdir), "plugin(%s) already exists!", name) + os.vcp(srcdir, dstdir) + cprint(" ${color.success}-> ${bright}%s${clear}", name) + end + os.tryrm(tmpdir) + + if installed_url then + local manifest = _load_manifest() or {} + manifest.urls = manifest.urls or {} + table.join2(manifest.urls, installed_url) + _save_manifest(manifest) + end cprint("${color.success}all plugins have been installed in ${bright}%s${clear}!", plugindir) end, catch { - function (_errors) - errors = _errors + function (errors) + raise(errors) end } } -- leave environment environment.leave() - if errors then - raise(errors) - end end -- update plugins @@ -283,7 +251,6 @@ function _update() -- enter environment environment.enter() - local errors try { function () @@ -295,40 +262,36 @@ function _update() local plugindir = _get_plugindir() for _, url in ipairs(urls) do cprint("updating plugins from ${bright}%s${clear} ..", url) - _with_tmpdir(function (tmpdir) - git.clone(url, {verbose = option.get("verbose"), outputdir = tmpdir}) - for _, filepath in ipairs(os.files(path.join(tmpdir, "*", "xmake.lua"))) do - local srcdir = path.directory(filepath) - local name = path.filename(srcdir) - local dstdir = _get_plugindir(name) - os.tryrm(dstdir) - os.vcp(srcdir, dstdir) - cprint(" ${color.success}-> ${bright}%s${clear}", name) - end - end) + local tmpdir = os.tmpfile() .. ".dir" + git.clone(url, {verbose = option.get("verbose"), outputdir = tmpdir}) + for _, filepath in ipairs(os.files(path.join(tmpdir, "*", "xmake.lua"))) do + local srcdir = path.directory(filepath) + local name = path.filename(srcdir) + local dstdir = _get_plugindir(name) + os.tryrm(dstdir) + os.vcp(srcdir, dstdir) + cprint(" ${color.success}-> ${bright}%s${clear}", name) + end + os.tryrm(tmpdir) end cprint("${color.success}all plugins have been updated in ${bright}%s${clear}!", plugindir) end, catch { - function (_errors) - errors = _errors + function (errors) + raise(errors) end } } -- leave environment environment.leave() - if errors then - raise(errors) - end end -- remove the given installed plugin function _remove() - local names = assert(option.get("plugins"), "please specify the plugin name to be removed!") - assert(#names == 1, "please specify only one plugin name to be removed!") - local name = _check_plugin_name(names[1]) + local name = assert(option.get("plugins"), "please specify the plugin name to be removed!") + assert(name ~= "" and name ~= "." and not name:find("..", 1, true) and not name:find("[/\\:]"), "invalid plugin name(%s)!", name) local dir = _get_plugindir(name) assert(os.isdir(dir), "plugin(%s) not found!", name) os.rmdir(dir) diff --git a/xmake/plugins/plugin/xmake.lua b/xmake/plugins/plugin/xmake.lua index aca6def8d..a1b7f61ba 100644 --- a/xmake/plugins/plugin/xmake.lua +++ b/xmake/plugins/plugin/xmake.lua @@ -30,7 +30,7 @@ task("plugin") {'r', "remove", "k", nil, "Remove the given installed plugin."}, {'l', "list", "k", nil, "List all installed plugins."}, {'c', "clear", "k", nil, "Clear all installed plugins."}, - {nil, "plugins", "vs", nil, "The plugin paths, urls or names.", + {nil, "plugins", "v", nil, "The plugins path, url or package name.", "e.g.", " $ xmake plugin --install https://github.com/xmake-io/xmake-plugins", " $ xmake plugin --install github:xmake-io/xmake-plugins", -- cgit v1.3.1 From ba5ca9bc89f2511a53266577a17a64fb26932a96 Mon Sep 17 00:00:00 2001 From: ruki Date: Thu, 30 Jul 2026 22:45:56 +0800 Subject: improve plugin list --- tests/plugins/repository/test.lua | 4 +- xmake/plugins/plugin/main.lua | 103 ++++++++++++++++++++++++++++---------- 2 files changed, 80 insertions(+), 27 deletions(-) (limited to 'tests/plugins') diff --git a/tests/plugins/repository/test.lua b/tests/plugins/repository/test.lua index c4c6c4c20..0c4df3227 100644 --- a/tests/plugins/repository/test.lua +++ b/tests/plugins/repository/test.lua @@ -1,4 +1,4 @@ -local global = import("core.base.global") +import("core.base.global") function _write_plugin(dir, name, text) io.writefile(path.join(dir, "xmake.lua"), string.format([[ @@ -56,6 +56,8 @@ function main(t) -- Feature: --list shows installed and available repository plugins local out = os.iorun("xmake plugin --list") + t:require(out:find("the built-in plugins:", 1, true)) + t:require(out:find("project", 1, true)) t:require(out:find(hello_name, 1, true)) t:require(out:find(formatter_name, 1, true)) t:require(out:find(available_name, 1, true)) diff --git a/xmake/plugins/plugin/main.lua b/xmake/plugins/plugin/main.lua index 515c5c6c4..0ea537a59 100644 --- a/xmake/plugins/plugin/main.lua +++ b/xmake/plugins/plugin/main.lua @@ -288,45 +288,96 @@ function _remove() cprint("${color.success}remove ${bright}%s${clear} ok!", name) end +-- get the description of a plugin from its xmake.lua menu +function _plugin_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("description%s*=%s*\"(.-)\"") + end + end +end + +-- collect plugins (each subdirectory containing xmake.lua) under the given root +function _collect_plugins(root, seen) + local entries = {} + for _, dir in ipairs(os.dirs(path.join(root, "*")) or {}) do + local name = path.filename(dir) + if os.isfile(path.join(dir, "xmake.lua")) and (not seen or not seen[name]) then + if seen then + seen[name] = true + end + table.insert(entries, {name = name, description = _plugin_description(dir)}) + end + end + return entries +end + +-- print a plugin entry with its description aligned on the right +function _print_plugin(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) + end +end + -- list all plugins function _list() local seen = {} - -- installed plugins + -- collect plugins from every source + local builtin = _collect_plugins(path.join(os.programdir(), "plugins"), seen) local plugindir = _get_plugindir() - cprint("${bright}the installed plugins:${clear}") - local found = false - for _, dir in ipairs(os.dirs(path.join(plugindir, "*")) or {}) do - if os.isfile(path.join(dir, "xmake.lua")) then - local name = path.filename(dir) - seen[name] = true - found = true - cprint(" ${bright}%s${clear}", name) + local installed = _collect_plugins(plugindir, seen) + local avail = {} + for _, repo in ipairs(_repositories()) do + table.join2(avail, _collect_plugins(path.join(repo:directory(), "plugins"), seen)) + end + + -- compute the alignment width from all plugin names + local width = 0 + for _, entries in ipairs({builtin, installed, avail}) do + for _, entry in ipairs(entries) do + width = math.max(width, #entry.name + 4) end end - if not found then + + -- built-in plugins + cprint("${bright}the built-in plugins:${clear}") + if #builtin > 0 then + for _, entry in ipairs(builtin) do + _print_plugin(entry.name, entry.description, width) + end + else + print(" (none)") + end + + -- installed plugins + cprint("${bright}the installed plugins:${clear}") + if #installed > 0 then + for _, entry in ipairs(installed) do + _print_plugin(entry.name, entry.description, width) + end + else print(" (none)") end -- plugins available in repositories (not yet installed) cprint("${bright}available in configured repositories:${clear}") - local avail = false - local repos = _repositories() - for _, repo in ipairs(repos) do - local rplugindir = path.join(repo:directory(), "plugins") - if os.isdir(rplugindir) then - for _, subdir in ipairs(os.dirs(path.join(rplugindir, "*")) or {}) do - local name = path.filename(subdir) - if os.isfile(path.join(subdir, "xmake.lua")) and not seen[name] then - seen[name] = true - avail = true - cprint(" - ${bright}%s${clear} ${dim}(run ${bright}xmake plugin --install %s${clear}${dim} to install)${clear}", name, name) - end - end + if #avail > 0 then + for _, entry in ipairs(avail) do + local note = string.format("(run xmake plugin --install %s to install)", entry.name) + _print_plugin(entry.name, entry.description, width, note) end - end - if not avail then - print(" (none)") + else + print(" (none)") end end -- cgit v1.3.1 From cb97bd6d308b83ab4eca3f0462d2690dd66960b5 Mon Sep 17 00:00:00 2001 From: ruki Date: Thu, 30 Jul 2026 22:48:00 +0800 Subject: improve plugin structure --- tests/plugins/repository/test.lua | 8 ++++---- xmake/plugins/plugin/main.lua | 15 +++++++++++---- 2 files changed, 15 insertions(+), 8 deletions(-) (limited to 'tests/plugins') diff --git a/tests/plugins/repository/test.lua b/tests/plugins/repository/test.lua index 0c4df3227..4f1347317 100644 --- a/tests/plugins/repository/test.lua +++ b/tests/plugins/repository/test.lua @@ -36,10 +36,10 @@ function main(t) end cleanup() - -- mock repository with installed and available plugins - _write_plugin(path.join(repodir, "plugins", hello_name), hello_name, "repo-ok") - _write_plugin(path.join(repodir, "plugins", formatter_name), formatter_name, "format-ok") - _write_plugin(path.join(repodir, "plugins", available_name), available_name, "available-ok") + -- mock repository with installed and available plugins (packages-like layout: plugins//) + _write_plugin(path.join(repodir, "plugins", hello_name:sub(1, 1), hello_name), hello_name, "repo-ok") + _write_plugin(path.join(repodir, "plugins", formatter_name:sub(1, 1), formatter_name), formatter_name, "format-ok") + _write_plugin(path.join(repodir, "plugins", available_name:sub(1, 1), available_name), available_name, "available-ok") os.mkdir(path.directory(cachefile)) local cache = os.isfile(cachefile) and io.load(cachefile) or {} cache.repositories = cache.repositories or {} diff --git a/xmake/plugins/plugin/main.lua b/xmake/plugins/plugin/main.lua index 0ea537a59..a21b4b264 100644 --- a/xmake/plugins/plugin/main.lua +++ b/xmake/plugins/plugin/main.lua @@ -81,8 +81,11 @@ function _save_manifest(manifest) end -- find a plugin directory in the given repository directory +-- +-- plugins in a repository follow the same layout as packages: +-- /plugins///xmake.lua function _find_plugin_in_repo(repodir, name) - local dir = path.join(repodir, "plugins", name) + local dir = path.join(repodir, "plugins", name:sub(1, 1):lower(), name) if os.isdir(dir) and os.isfile(path.join(dir, "xmake.lua")) then return dir end @@ -300,9 +303,13 @@ function _plugin_description(dir) end -- collect plugins (each subdirectory containing xmake.lua) under the given root -function _collect_plugins(root, seen) +-- +-- built-in/installed plugins are flat (/), while plugins in a +-- repository follow the packages layout (//) +function _collect_plugins(root, seen, nested) local entries = {} - for _, dir in ipairs(os.dirs(path.join(root, "*")) or {}) do + local pattern = nested and path.join(root, "*", "*") or path.join(root, "*") + for _, dir in ipairs(os.dirs(pattern) or {}) do local name = path.filename(dir) if os.isfile(path.join(dir, "xmake.lua")) and (not seen or not seen[name]) then if seen then @@ -338,7 +345,7 @@ function _list() local installed = _collect_plugins(plugindir, seen) local avail = {} for _, repo in ipairs(_repositories()) do - table.join2(avail, _collect_plugins(path.join(repo:directory(), "plugins"), seen)) + table.join2(avail, _collect_plugins(path.join(repo:directory(), "plugins"), seen, true)) end -- compute the alignment width from all plugin names -- cgit v1.3.1 From 8d7a74306556a4c292c0345f1a9aa833116ebcec Mon Sep 17 00:00:00 2001 From: ruki Date: Sat, 1 Aug 2026 08:53:30 +0800 Subject: improve tests --- tests/plugins/repository/test.lua | 135 +++++++++++++++++++++----------------- xmake/plugins/plugin/main.lua | 26 +++----- xmake/plugins/plugin/xmake.lua | 12 ++-- 3 files changed, 90 insertions(+), 83 deletions(-) (limited to 'tests/plugins') diff --git a/tests/plugins/repository/test.lua b/tests/plugins/repository/test.lua index 4f1347317..f1cfba8c7 100644 --- a/tests/plugins/repository/test.lua +++ b/tests/plugins/repository/test.lua @@ -1,88 +1,105 @@ import("core.base.global") -function _write_plugin(dir, name, text) +-- write a minimal plugin that prints its name when run +function _write_plugin(dir, name) io.writefile(path.join(dir, "xmake.lua"), string.format([[ task("%s") set_category("plugin") on_run("main") - set_menu {usage = "xmake %s"} -]], name, name)) - io.writefile(path.join(dir, "main.lua"), string.format([[function main() print("%s") end]], text)) + set_menu {usage = "xmake %s", description = "say hello from %s"} +]], name, name, name)) + io.writefile(path.join(dir, "main.lua"), string.format([[function main() print("%s") end]], name)) end -function main(t) +-- create a temporary plugin repository (packages layout: plugins//) and register it +-- +-- @return reponame, names, cleanup +function _mock_repo(basenames) local suffix = path.filename(os.tmpfile()):gsub("[^%w]", "") - local reponame = "plugin-test-repository-" .. suffix - local hello_name = "plugin-test-hello-" .. suffix - local formatter_name = "plugin-test-formatter-" .. suffix - local available_name = "plugin-test-available-" .. suffix - local local_name = "plugin-test-local-" .. suffix - local repodir = os.tmpfile() .. ".plugins-repository" - local localdir = path.join(os.tmpfile() .. ".local-plugin", local_name) - local plugindir = path.join(global.directory(), "plugins") + local reponame = "plugin-test-repo-" .. suffix + local repodir = os.tmpfile() .. ".plugin-repo" + local names = {} + for _, base in ipairs(basenames) do + local name = base .. "-" .. suffix + _write_plugin(path.join(repodir, "plugins", name:sub(1, 1), name), name) + table.insert(names, name) + end + + -- register the repository into the cache local cachefile = path.join(global.cachedir(), "repository") + local cache = os.isfile(cachefile) and io.load(cachefile) or {} + cache.repositories = cache.repositories or {} + cache.repositories[reponame] = {repodir} + io.save(cachefile, cache) - -- reset test state local function cleanup() - for _, name in ipairs({hello_name, formatter_name, available_name, local_name}) do - os.tryrm(path.join(plugindir, name)) + for _, name in ipairs(names) do + os.tryrm(path.join(global.directory(), "plugins", name)) end local cache = os.isfile(cachefile) and io.load(cachefile) or {} - cache.repositories = cache.repositories or {} - cache.repositories[reponame] = nil + if cache.repositories then + cache.repositories[reponame] = nil + end io.save(cachefile, cache) os.tryrm(repodir) - os.tryrm(path.directory(localdir)) end - cleanup() + return reponame, names, cleanup +end - -- mock repository with installed and available plugins (packages-like layout: plugins//) - _write_plugin(path.join(repodir, "plugins", hello_name:sub(1, 1), hello_name), hello_name, "repo-ok") - _write_plugin(path.join(repodir, "plugins", formatter_name:sub(1, 1), formatter_name), formatter_name, "format-ok") - _write_plugin(path.join(repodir, "plugins", available_name:sub(1, 1), available_name), available_name, "available-ok") - os.mkdir(path.directory(cachefile)) - local cache = os.isfile(cachefile) and io.load(cachefile) or {} - cache.repositories = cache.repositories or {} - cache.repositories[reponame] = {repodir} - io.save(cachefile, cache) +-- install a plugin 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] - -- Feature: install by plain name from repository - os.runv("xmake", {"plugin", "--install", hello_name}) - os.runv("xmake", {hello_name}) + -- install by plain name (searched across all repositories) + os.runv("xmake", {"plugin", "--install", name}) + t:require(os.iorunv("xmake", {name}):find(name, 1, true)) - -- Feature: install by repo@name format - os.runv("xmake", {"plugin", "--install", reponame .. "@" .. formatter_name}) - os.runv("xmake", {formatter_name}) + -- reinstall by repo@name + os.runv("xmake", {"plugin", "--remove", name}) + os.runv("xmake", {"plugin", "--install", reponame .. "@" .. name}) + t:require(os.iorunv("xmake", {name}):find(name, 1, true)) - -- Feature: --list shows installed and available repository plugins - local out = os.iorun("xmake plugin --list") - t:require(out:find("the built-in plugins:", 1, true)) - t:require(out:find("project", 1, true)) - t:require(out:find(hello_name, 1, true)) - t:require(out:find(formatter_name, 1, true)) - t:require(out:find(available_name, 1, true)) - t:require(out:find("xmake plugin --install " .. available_name, 1, true)) + os.runv("xmake", {"plugin", "--remove", name}) + cleanup() +end + +-- install a plugin from a local directory, then remove it +function test_install_from_local(t) + local suffix = path.filename(os.tmpfile()):gsub("[^%w]", "") + local name = "hello-local-" .. suffix + local dir = path.join(os.tmpfile() .. ".plugin-local", name) + _write_plugin(dir, name) - -- Feature: install from local directory - _write_plugin(localdir, local_name, "local-ok") - os.runv("xmake", {"plugin", "--install", localdir}) - os.runv("xmake", {local_name}) + os.runv("xmake", {"plugin", "--install", dir}) + t:require(os.iorunv("xmake", {name}):find(name, 1, true)) - out = os.iorun("xmake plugin --list") - t:require(out:find(local_name, 1, true)) + -- the removed plugin should no longer be runnable + os.runv("xmake", {"plugin", "--remove", name}) + t:require_not(try { function () os.runv("xmake", {name}); return true end }) - -- Feature: remove plugin - os.runv("xmake", {"plugin", "--remove", local_name}) - out = os.iorun("xmake plugin --list") - t:require_not(out:find(local_name, 1, true)) + os.tryrm(path.directory(dir)) +end - -- Feature: install non-existent plugin fails gracefully - local ok = try { function () os.runv("xmake", {"plugin", "--install", "plugin-test-missing-" .. suffix}) return true end } - t:require_not(ok) +-- --list shows the built-in, installed and available plugins +function test_list(t) + local reponame, names, cleanup = _mock_repo({"hello", "world"}) - -- Feature: reject plugin name traversal - ok = try { function () os.runv("xmake", {"plugin", "--install", reponame .. "@.."}) return true end } - t:require_not(ok) + -- install the first plugin, leave the second only available + os.runv("xmake", {"plugin", "--install", names[1]}) + local out = os.iorunv("xmake", {"plugin", "--list"}) + t:require(out:find("the built-in plugins:", 1, true)) + t:require(out:find("project", 1, true)) + t:require(out:find(names[1], 1, true)) + t:require(out:find(names[2], 1, true)) + t:require(out:find("xmake plugin --install " .. names[2], 1, true)) + os.runv("xmake", {"plugin", "--remove", names[1]}) cleanup() end + +-- invalid installs should fail +function test_install_invalid(t) + t:require_not(try { function () os.runv("xmake", {"plugin", "--install", "plugin-test-missing"}); return true end }) + t:require_not(try { function () os.runv("xmake", {"plugin", "--install", "somerepo@.."}); return true end }) +end diff --git a/xmake/plugins/plugin/main.lua b/xmake/plugins/plugin/main.lua index 48bae9cde..5158352c0 100644 --- a/xmake/plugins/plugin/main.lua +++ b/xmake/plugins/plugin/main.lua @@ -74,17 +74,17 @@ function _install_plugins_from_repo(name, reponame) raise("plugin(%s): not found in any repository! try ${bright}xrepo update-repo${clear} first.", name) end --- install a plugin from a local directory -function _install_from_local(dir) +-- install a single plugin from a source directory (as the given name, default to the directory name) +function _install_from_local(dir, name) assert(os.isdir(dir) and os.isfile(path.join(dir, "xmake.lua")), "plugin path(%s): ${bright}xmake.lua${clear} not found!", dir) - local name = path.filename(path.absolute(dir)) + name = name or path.filename(path.absolute(dir)) local dstdir = _get_plugindir(name) assert(not os.isdir(dstdir), "plugin(%s) already exists!", name) os.vcp(dir, dstdir) - cprint("${color.success}install ${bright}%s${clear} from ${bright}%s${clear} ok!", name, dir) + cprint("${color.success}install ${bright}%s${clear} ok!", name) end --- install a plugin from a git url or github shortcut +-- install a single plugin from a git url or github shortcut, e.g. https://github.com/xmake-io/hello-world function _install_from_git(url) local branch if url:startswith("github:") then @@ -95,9 +95,11 @@ function _install_from_git(url) end url = git.asgiturl(url) end + local name = (path.filename(url):gsub("%.git$", "")) local tmpdir = os.tmpfile() .. ".dir" git.clone(url, {verbose = option.get("verbose"), branch = branch, outputdir = tmpdir}) - _copy_plugins_from_dir(tmpdir) + os.tryrm(path.join(tmpdir, ".git")) + _install_from_local(tmpdir, name) os.tryrm(tmpdir) end @@ -131,18 +133,6 @@ function _install_one(name) _install_plugins_from_repo(name) end --- copy every plugin found under the cloned directory into the plugins directory -function _copy_plugins_from_dir(tmpdir) - for _, filepath in ipairs(os.files(path.join(tmpdir, "*", "xmake.lua"))) do - local srcdir = path.directory(filepath) - local name = path.filename(srcdir) - local dstdir = _get_plugindir(name) - assert(not os.isdir(dstdir), "plugin(%s) already exists!", name) - os.vcp(srcdir, dstdir) - cprint(" ${color.success}-> ${bright}%s${clear}", name) - end -end - -- install plugins function _install() local names = assert(option.get("plugins"), "please specify the plugins to be installed!") diff --git a/xmake/plugins/plugin/xmake.lua b/xmake/plugins/plugin/xmake.lua index 8da06a92e..4aab3daa4 100644 --- a/xmake/plugins/plugin/xmake.lua +++ b/xmake/plugins/plugin/xmake.lua @@ -31,13 +31,13 @@ task("plugin") {'c', "clear", "k", nil, "Clear all installed plugins."}, {nil, "plugins", "vs", nil, "The plugin paths, urls or names.", "e.g.", - " $ xmake plugin --install https://github.com/xmake-io/xmake-plugins", - " $ xmake plugin --install github:xmake-io/xmake-plugins", - " $ xmake plugin --install github:xmake-io/xmake-plugins#dev", + " $ xmake plugin --install https://github.com/myrepo/hello", + " $ xmake plugin --install github:myrepo/hello", + " $ xmake plugin --install github:myrepo/hello#dev", " $ xmake plugin --install /tmp/my-plugin", - " $ xmake plugin --install xmake-repo@hello-world", - " $ xmake plugin --install hello-world", - " $ xmake plugin --remove hello-world", + " $ xmake plugin --install xmake-repo@hello", + " $ xmake plugin --install hello", + " $ xmake plugin --remove hello", " $ xmake plugin --list"} } } -- cgit v1.3.1