diff options
| author | Saikari <[email protected]> | 2026-07-27 11:51:12 +0300 |
|---|---|---|
| committer | ruki <[email protected]> | 2026-07-30 10:17:31 +0800 |
| commit | 0376ff7ab486d773458886982f465897ca18a978 (patch) | |
| tree | 1cdc17f77c73df3160b7ef24fefe09a9105629cc | |
| parent | b4e04c99d5e3d3a09ddec4ac1ceff016e05c1c64 (diff) | |
Enhance plugin management by adding support for installing plugins from GitHub and local directories, and streamline repository handling in the task system.
| -rw-r--r-- | tests/plugins/repository/test.lua | 136 | ||||
| -rw-r--r-- | xmake/core/base/task.lua | 21 | ||||
| -rw-r--r-- | xmake/plugins/plugin/main.lua | 229 | ||||
| -rw-r--r-- | xmake/plugins/plugin/xmake.lua | 4 |
4 files changed, 245 insertions, 145 deletions
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", |
