summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSaikari <[email protected]>2026-07-27 11:51:12 +0300
committerruki <[email protected]>2026-07-30 10:17:31 +0800
commit0376ff7ab486d773458886982f465897ca18a978 (patch)
tree1cdc17f77c73df3160b7ef24fefe09a9105629cc
parentb4e04c99d5e3d3a09ddec4ac1ceff016e05c1c64 (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.lua136
-rw-r--r--xmake/core/base/task.lua21
-rw-r--r--xmake/plugins/plugin/main.lua229
-rw-r--r--xmake/plugins/plugin/xmake.lua4
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",