summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorruki <[email protected]>2026-08-08 22:41:30 +0800
committerruki <[email protected]>2026-08-09 18:28:59 +0800
commit8564ea66201d1398cb8ee5b91efc784a379c4a64 (patch)
treea1f909bc0c06a456d423e299733ab10d86cbeb47
parent4fc05750f6da0568df7a0fa836e114128d3ba849 (diff)
fix plugin bug and add description
-rw-r--r--tests/actions/addon/test.lua167
-rw-r--r--tests/plugins/repository/test.lua118
-rw-r--r--xmake/actions/addon/main.lua5
-rw-r--r--xmake/core/package/addon.lua19
-rw-r--r--xmake/core/package/package.lua9
-rw-r--r--xmake/modules/private/action/require/impl/actions/install.lua3
6 files changed, 193 insertions, 128 deletions
diff --git a/tests/actions/addon/test.lua b/tests/actions/addon/test.lua
new file mode 100644
index 000000000..17a5dc33c
--- /dev/null
+++ b/tests/actions/addon/test.lua
@@ -0,0 +1,167 @@
+import("core.base.global")
+
+-- write a minimal plugin that prints its name when run
+--
+-- the plugins of an addon are placed in its `plugins` payload directory,
+-- e.g. <addondir>/plugins/<name>/xmake.lua
+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", description = "say hello from %s"}
+]], name, name, name))
+ io.writefile(path.join(dir, "main.lua"), string.format([[function main() print("%s") end]], name))
+end
+
+-- write a minimal template into the `templates` payload directory of an addon,
+-- e.g. <addondir>/templates/<language>/<templateid>/xmake.lua
+function _write_template(dir, lang, templateid)
+ local templatedir = path.join(dir, "templates", lang, templateid)
+ io.writefile(path.join(templatedir, "xmake.lua"), [[
+target("${TARGET_NAME}")
+ set_kind("binary")
+ add_files("src/*.c")
+]])
+ io.writefile(path.join(templatedir, "src", "main.c"), [[
+int main(int argc, char** argv) { return 0; }
+]])
+end
+
+-- write an addon payload directory, it provides a plugin and a template
+function _write_addon(dir, name)
+ _write_plugin(path.join(dir, "plugins", name), name)
+ _write_template(dir, "c", name)
+end
+
+-- write an addon package description, its payloads are placed in the `src` directory
+--
+-- addons in a repository are described as packages, e.g. <repodir>/addons/<first-letter>/<name>/xmake.lua
+function _write_addon_package(dir, name)
+ io.writefile(path.join(dir, "xmake.lua"), string.format([[
+package("%s")
+ set_kind("addon")
+ set_description("say hello from %s")
+ set_sourcedir(path.join(os.scriptdir(), "src"))
+]], name, name))
+ _write_addon(path.join(dir, "src"), name)
+end
+
+-- create a temporary addon repository (packages layout: addons/<first-letter>/<name>) and register it
+--
+-- @return reponame, names, cleanup
+function _mock_repo(basenames)
+ local suffix = path.filename(os.tmpfile()):gsub("[^%w]", "")
+ local reponame = "addon-test-repo-" .. suffix
+ local repodir = os.tmpfile() .. ".addon-repo"
+ local names = {}
+ for _, base in ipairs(basenames) do
+ local name = base .. "-" .. suffix
+ _write_addon_package(path.join(repodir, "addons", 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)
+
+ local function cleanup()
+ for _, name in ipairs(names) do
+ os.tryrm(path.join(global.directory(), "addons", name))
+ try { function () os.runv("xmake", {"addon", "--remove", name}) end }
+ end
+ local cache = os.isfile(cachefile) and io.load(cachefile) or {}
+ if cache.repositories then
+ cache.repositories[reponame] = nil
+ end
+ io.save(cachefile, cache)
+ os.tryrm(repodir)
+ end
+ return reponame, names, cleanup
+end
+
+-- install an addon from a repository, by plain name and by repo@name
+function test_install_from_repo(t)
+ local reponame, names, cleanup = _mock_repo({"hello"})
+ local name = names[1]
+
+ -- install by plain name (searched across all repositories)
+ os.runv("xmake", {"addon", "--install", "-y", name})
+ t:require(os.iorunv("xmake", {name}):find(name, 1, true))
+
+ -- reinstall by repo@name
+ os.runv("xmake", {"addon", "--remove", name})
+ os.runv("xmake", {"addon", "--install", "-y", reponame .. "@" .. name})
+ t:require(os.iorunv("xmake", {name}):find(name, 1, true))
+
+ os.runv("xmake", {"addon", "--remove", name})
+ cleanup()
+end
+
+-- the templates of an installed addon can be used by `xmake create`
+function test_install_templates(t)
+ local _, names, cleanup = _mock_repo({"hello"})
+ local name = names[1]
+ os.runv("xmake", {"addon", "--install", "-y", name})
+
+ -- this template should be listed and grouped by its addon name
+ local out = os.iorunv("xmake", {"create", "--list"})
+ t:require(out:find(name, 1, true))
+
+ -- we can create a new project from it
+ local projectdir = os.tmpfile() .. ".addon-project"
+ os.tryrm(projectdir)
+ os.runv("xmake", {"create", "-l", "c", "-t", name, "-P", projectdir})
+ t:require(os.isfile(path.join(projectdir, "xmake.lua")))
+ t:require(os.isfile(path.join(projectdir, "src", "main.c")))
+
+ os.tryrm(projectdir)
+ os.runv("xmake", {"addon", "--remove", name})
+ cleanup()
+end
+
+-- install an addon 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() .. ".addon-local", name)
+ _write_addon(dir, name)
+
+ os.runv("xmake", {"addon", "--install", dir})
+ t:require(os.iorunv("xmake", {name}):find(name, 1, true))
+
+ -- the removed addon should no longer be runnable
+ os.runv("xmake", {"addon", "--remove", name})
+ t:require_not(try { function () os.runv("xmake", {name}); return true end })
+
+ os.tryrm(path.directory(dir))
+end
+
+-- --list shows the installed and available addons
+function test_list(t)
+ local _, names, cleanup = _mock_repo({"hello", "world"})
+
+ -- install the first addon, leave the second only available
+ os.runv("xmake", {"addon", "--install", "-y", names[1]})
+ local out = os.iorunv("xmake", {"addon", "--list"})
+ t:require(out:find("the installed addons:", 1, true))
+ t:require(out:find(names[1], 1, true))
+ t:require(out:find(names[2], 1, true))
+ t:require(out:find("xmake addon --install " .. names[2], 1, true))
+
+ -- the payloads of the installed addon should be shown, e.g. (latest, plugins, templates)
+ t:require(out:find("plugins", 1, true))
+ t:require(out:find("templates", 1, true))
+
+ os.runv("xmake", {"addon", "--remove", names[1]})
+ cleanup()
+end
+
+-- invalid installs should fail
+function test_install_invalid(t)
+ t:require_not(try { function () os.runv("xmake", {"addon", "--install", "-y", "addon-test-missing"}); return true end })
+ t:require_not(try { function () os.runv("xmake", {"addon", "--install", "-y", "somerepo@.."}); return true end })
+end
diff --git a/tests/plugins/repository/test.lua b/tests/plugins/repository/test.lua
deleted file mode 100644
index 7b1619d5e..000000000
--- a/tests/plugins/repository/test.lua
+++ /dev/null
@@ -1,118 +0,0 @@
-import("core.base.global")
-
--- 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", description = "say hello from %s"}
-]], name, name, name))
- io.writefile(path.join(dir, "main.lua"), string.format([[function main() print("%s") end]], name))
-end
-
--- write a plugin package description, the plugin sources are placed in its `src` directory
---
--- plugins in a repository are described as packages, e.g. <repodir>/plugins/<first-letter>/<name>/xmake.lua
-function _write_plugin_package(dir, name)
- io.writefile(path.join(dir, "xmake.lua"), string.format([[
-package("%s")
- set_kind("plugin")
- set_description("say hello from %s")
- set_sourcedir(path.join(os.scriptdir(), "src"))
-]], name, name))
- _write_plugin(path.join(dir, "src"), name)
-end
-
--- create a temporary plugin repository (packages layout: plugins/<first-letter>/<name>) and register it
---
--- @return reponame, names, cleanup
-function _mock_repo(basenames)
- local suffix = path.filename(os.tmpfile()):gsub("[^%w]", "")
- 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_package(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)
-
- local function cleanup()
- 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 {}
- if cache.repositories then
- cache.repositories[reponame] = nil
- end
- io.save(cachefile, cache)
- os.tryrm(repodir)
- end
- return reponame, names, cleanup
-end
-
--- 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]
-
- -- install by plain name (searched across all repositories)
- os.runv("xmake", {"plugin", "--install", "-y", name})
- t:require(os.iorunv("xmake", {name}):find(name, 1, true))
-
- -- reinstall by repo@name
- os.runv("xmake", {"plugin", "--remove", name})
- os.runv("xmake", {"plugin", "--install", "-y", reponame .. "@" .. name})
- t:require(os.iorunv("xmake", {name}):find(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)
-
- os.runv("xmake", {"plugin", "--install", dir})
- t:require(os.iorunv("xmake", {name}):find(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 })
-
- os.tryrm(path.directory(dir))
-end
-
--- --list shows the built-in, installed and available plugins
-function test_list(t)
- local reponame, names, cleanup = _mock_repo({"hello", "world"})
-
- -- install the first plugin, leave the second only available
- os.runv("xmake", {"plugin", "--install", "-y", 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", "-y", "plugin-test-missing"}); return true end })
- t:require_not(try { function () os.runv("xmake", {"plugin", "--install", "-y", "somerepo@.."}); return true end })
-end
diff --git a/xmake/actions/addon/main.lua b/xmake/actions/addon/main.lua
index d072cec7f..52cb2376b 100644
--- a/xmake/actions/addon/main.lua
+++ b/xmake/actions/addon/main.lua
@@ -179,7 +179,8 @@ end
function _collect_installed_addons()
local entries = {}
for name, addoninfo in pairs(addon.rescan()) do
- table.insert(entries, {name = name, version = addoninfo.version, payloads = addoninfo.payloads})
+ table.insert(entries, {name = name, version = addoninfo.version,
+ description = addoninfo.description, payloads = addoninfo.payloads})
end
table.sort(entries, function (a, b) return a.name < b.name end)
return entries
@@ -237,7 +238,7 @@ function _list()
if #installed > 0 then
for _, entry in ipairs(installed) do
local note = string.format("(%s, %s)", entry.version, table.concat(entry.payloads, ", "))
- _print_addon(entry.name, nil, width, note)
+ _print_addon(entry.name, entry.description, width, note)
end
else
print(" (none)")
diff --git a/xmake/core/package/addon.lua b/xmake/core/package/addon.lua
index 35966853d..d433bdf8a 100644
--- a/xmake/core/package/addon.lua
+++ b/xmake/core/package/addon.lua
@@ -170,11 +170,15 @@ end
--
-- @param name the addon name
-- @param version the addon version, e.g. "1.0.1", "latest"
+-- @param opt the options, e.g. {description = "..."}
--
-function addon.register(name, version)
+function addon.register(name, version, opt)
+ opt = opt or {}
local dirname = addon.dirname(name)
local addons = addon.addons()
- addons[dirname] = {version = version, payloads = addon.payloads_of(path.join(addon.installdir(), dirname, version))}
+ addons[dirname] = {version = version,
+ description = opt.description,
+ payloads = addon.payloads_of(path.join(addon.installdir(), dirname, version))}
addon._save(addons)
end
@@ -193,11 +197,20 @@ end
-- it's only used to repair the registry file, e.g. the user removed some addon directories manually
--
function addon.rescan()
+ local oldaddons = addon.addons()
local addons = {}
for _, versiondir in ipairs(os.dirs(path.join(addon.installdir(), "*", "*"))) do
local payloads = addon.payloads_of(versiondir)
if #payloads > 0 then
- addons[path.filename(path.directory(versiondir))] = {version = path.filename(versiondir), payloads = payloads}
+ local dirname = path.filename(path.directory(versiondir))
+ local version = path.filename(versiondir)
+ -- we need to keep the description, we cannot get it from the installed payloads
+ local oldaddoninfo = oldaddons[dirname]
+ local description
+ if oldaddoninfo and oldaddoninfo.version == version then
+ description = oldaddoninfo.description
+ end
+ addons[dirname] = {version = version, description = description, payloads = payloads}
end
end
addon._save(addons)
diff --git a/xmake/core/package/package.lua b/xmake/core/package/package.lua
index 2b56ee332..9d0cdc49d 100644
--- a/xmake/core/package/package.lua
+++ b/xmake/core/package/package.lua
@@ -1216,10 +1216,11 @@ function _instance:_rawenvs()
end
-- add plugin env for on_test
- if self:is_addon() then
- -- e.g. ~/.xmake/addons/<name>/<version>/plugins
- envs.XMAKE_PLUGIN_DIRS = path.join(self:installdir(), "plugins")
- elseif self:is_plugin() then
+ --
+ -- @note we need not do it for the addon packages, they are registered
+ -- to the addons registry after installing, and xmake can find their payloads directly
+ --
+ if self:is_plugin() then
envs.XMAKE_PLUGIN_DIRS = path.directory(self:installdir())
end
self._RAWENVS = envs
diff --git a/xmake/modules/private/action/require/impl/actions/install.lua b/xmake/modules/private/action/require/impl/actions/install.lua
index 7e60b16b6..40ca225f0 100644
--- a/xmake/modules/private/action/require/impl/actions/install.lua
+++ b/xmake/modules/private/action/require/impl/actions/install.lua
@@ -503,7 +503,8 @@ function main(package)
-- register this addon, so that xmake can find its payloads, e.g. plugins
if package:is_addon() then
- addon.register(package:name(), package:version_str() or "latest")
+ addon.register(package:name(), package:version_str() or "latest",
+ {description = package:description()})
end
installed_now = true
end