summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorruki <[email protected]>2026-08-11 23:45:05 +0800
committerruki <[email protected]>2026-08-11 23:45:05 +0800
commit7ffcc9f8e2a98d63f7f6880b9a3e4553439583c6 (patch)
treef2bf7c4e0e9806fd157cc3243a45f691fd282988
parent9e6abef8b0a26d7098d79749dd68b5a2590512c4 (diff)
add global modules and rewrite addon tests
-rw-r--r--tests/actions/addon/custom-include/addon.lua2
-rw-r--r--tests/actions/addon/custom-include/includes/check/xmake.lua4
-rw-r--r--tests/actions/addon/custom-module/addon.lua2
-rw-r--r--tests/actions/addon/custom-module/modules/greeting.lua3
-rw-r--r--tests/actions/addon/custom-plugin/addon.lua2
-rw-r--r--tests/actions/addon/custom-plugin/plugins/hello_addon/main.lua5
-rw-r--r--tests/actions/addon/custom-plugin/plugins/hello_addon/xmake.lua5
-rw-r--r--tests/actions/addon/custom-rule/addon.lua2
-rw-r--r--tests/actions/addon/custom-rule/modules/greeting.lua3
-rw-r--r--tests/actions/addon/custom-rule/rules/hello/xmake.lua6
-rw-r--r--tests/actions/addon/custom-template/addon.lua2
-rw-r--r--tests/actions/addon/custom-template/templates/c/customaddon/hello/src/main.c6
-rw-r--r--tests/actions/addon/custom-template/templates/c/customaddon/hello/xmake.lua (renamed from tests/actions/addon/demo-addon/src/templates/c/demoaddon/hello/xmake.lua)2
-rw-r--r--tests/actions/addon/custom-toolchain/addon.lua6
-rw-r--r--tests/actions/addon/custom-toolchain/src/modules/core/tools/mycl6x.lua7
-rw-r--r--tests/actions/addon/custom-toolchain/src/modules/detect/tools/find_mycl6x.lua15
-rw-r--r--tests/actions/addon/custom-toolchain/src/toolchains/my-c6000/xmake.lua19
-rw-r--r--tests/actions/addon/demo-addon-dep/addon.lua3
-rw-r--r--tests/actions/addon/demo-addon-dep/rules/dep/xmake.lua10
-rw-r--r--tests/actions/addon/demo-addon/addon.lua3
-rw-r--r--tests/actions/addon/demo-addon/src/includes/check/xmake.lua7
-rw-r--r--tests/actions/addon/demo-addon/src/modules/greeting.lua3
-rw-r--r--tests/actions/addon/demo-addon/src/plugins/demo_hello/main.lua6
-rw-r--r--tests/actions/addon/demo-addon/src/plugins/demo_hello/xmake.lua10
-rw-r--r--tests/actions/addon/demo-addon/src/rules/app/xmake.lua9
-rw-r--r--tests/actions/addon/demo-addon/src/rules/base/xmake.lua4
-rw-r--r--tests/actions/addon/demo-addon/src/templates/c/demoaddon/hello/src/main.c3
-rw-r--r--tests/actions/addon/demo-addon/src/toolchains/demo/xmake.lua6
-rw-r--r--tests/actions/addon/demo-addon/tests/hello_test.lua1
-rw-r--r--tests/actions/addon/test.lua230
-rw-r--r--xmake/actions/addon/main.lua9
-rw-r--r--xmake/core/package/addon.lua41
-rw-r--r--xmake/core/sandbox/modules/import/core/sandbox/module.lua37
-rw-r--r--xmake/modules/private/action/require/impl/actions/install.lua3
34 files changed, 291 insertions, 185 deletions
diff --git a/tests/actions/addon/custom-include/addon.lua b/tests/actions/addon/custom-include/addon.lua
new file mode 100644
index 000000000..71d01b5c2
--- /dev/null
+++ b/tests/actions/addon/custom-include/addon.lua
@@ -0,0 +1,2 @@
+addon("custom-include")
+ set_description("the addon which provides a custom includes file")
diff --git a/tests/actions/addon/custom-include/includes/check/xmake.lua b/tests/actions/addon/custom-include/includes/check/xmake.lua
new file mode 100644
index 000000000..953f7ba73
--- /dev/null
+++ b/tests/actions/addon/custom-include/includes/check/xmake.lua
@@ -0,0 +1,4 @@
+option("myoption")
+ set_default(true)
+ set_description("The option of the custom-include addon.")
+print("custom-include: includes check is loaded")
diff --git a/tests/actions/addon/custom-module/addon.lua b/tests/actions/addon/custom-module/addon.lua
new file mode 100644
index 000000000..604dbc645
--- /dev/null
+++ b/tests/actions/addon/custom-module/addon.lua
@@ -0,0 +1,2 @@
+addon("custom-module")
+ set_description("the addon which provides a custom module")
diff --git a/tests/actions/addon/custom-module/modules/greeting.lua b/tests/actions/addon/custom-module/modules/greeting.lua
new file mode 100644
index 000000000..843dddfc2
--- /dev/null
+++ b/tests/actions/addon/custom-module/modules/greeting.lua
@@ -0,0 +1,3 @@
+function main(name)
+ return "hello from custom-module: " .. name
+end
diff --git a/tests/actions/addon/custom-plugin/addon.lua b/tests/actions/addon/custom-plugin/addon.lua
new file mode 100644
index 000000000..ba4515c78
--- /dev/null
+++ b/tests/actions/addon/custom-plugin/addon.lua
@@ -0,0 +1,2 @@
+addon("custom-plugin")
+ set_description("the addon which provides a custom plugin")
diff --git a/tests/actions/addon/custom-plugin/plugins/hello_addon/main.lua b/tests/actions/addon/custom-plugin/plugins/hello_addon/main.lua
new file mode 100644
index 000000000..0cf3a0386
--- /dev/null
+++ b/tests/actions/addon/custom-plugin/plugins/hello_addon/main.lua
@@ -0,0 +1,5 @@
+import("core.base.option")
+
+function main()
+ print("hello from custom-plugin: %s", option.get("name") or "world")
+end
diff --git a/tests/actions/addon/custom-plugin/plugins/hello_addon/xmake.lua b/tests/actions/addon/custom-plugin/plugins/hello_addon/xmake.lua
new file mode 100644
index 000000000..7976a1d0e
--- /dev/null
+++ b/tests/actions/addon/custom-plugin/plugins/hello_addon/xmake.lua
@@ -0,0 +1,5 @@
+task("hello_addon")
+ set_category("plugin")
+ set_menu {usage = "xmake hello_addon", description = "Say hello from the addon.",
+ options = {{'n', "name", "kv", nil, "Set the name."}}}
+ on_run("main")
diff --git a/tests/actions/addon/custom-rule/addon.lua b/tests/actions/addon/custom-rule/addon.lua
new file mode 100644
index 000000000..2e9b68ab5
--- /dev/null
+++ b/tests/actions/addon/custom-rule/addon.lua
@@ -0,0 +1,2 @@
+addon("custom-rule")
+ set_description("the addon which provides a custom rule")
diff --git a/tests/actions/addon/custom-rule/modules/greeting.lua b/tests/actions/addon/custom-rule/modules/greeting.lua
new file mode 100644
index 000000000..f247d21ca
--- /dev/null
+++ b/tests/actions/addon/custom-rule/modules/greeting.lua
@@ -0,0 +1,3 @@
+function main(name)
+ return "hello from custom-rule: " .. name
+end
diff --git a/tests/actions/addon/custom-rule/rules/hello/xmake.lua b/tests/actions/addon/custom-rule/rules/hello/xmake.lua
new file mode 100644
index 000000000..07638387d
--- /dev/null
+++ b/tests/actions/addon/custom-rule/rules/hello/xmake.lua
@@ -0,0 +1,6 @@
+rule("hello")
+ on_load(function (target)
+ -- the addon code refers to its own resources with `@self`
+ import("@self.greeting")
+ print("custom-rule: %s", greeting(target:name()))
+ end)
diff --git a/tests/actions/addon/custom-template/addon.lua b/tests/actions/addon/custom-template/addon.lua
new file mode 100644
index 000000000..f01eda9c9
--- /dev/null
+++ b/tests/actions/addon/custom-template/addon.lua
@@ -0,0 +1,2 @@
+addon("custom-template")
+ set_description("the addon which provides a custom project template")
diff --git a/tests/actions/addon/custom-template/templates/c/customaddon/hello/src/main.c b/tests/actions/addon/custom-template/templates/c/customaddon/hello/src/main.c
new file mode 100644
index 000000000..115a2a792
--- /dev/null
+++ b/tests/actions/addon/custom-template/templates/c/customaddon/hello/src/main.c
@@ -0,0 +1,6 @@
+#include <stdio.h>
+
+int main(int argc, char** argv) {
+ printf("hello from the custom-template addon!\n");
+ return 0;
+}
diff --git a/tests/actions/addon/demo-addon/src/templates/c/demoaddon/hello/xmake.lua b/tests/actions/addon/custom-template/templates/c/customaddon/hello/xmake.lua
index f9ea6e8d2..33c36ec7e 100644
--- a/tests/actions/addon/demo-addon/src/templates/c/demoaddon/hello/xmake.lua
+++ b/tests/actions/addon/custom-template/templates/c/customaddon/hello/xmake.lua
@@ -1,3 +1,5 @@
+add_rules("mode.debug", "mode.release")
+
target("${TARGET_NAME}")
set_kind("binary")
add_files("src/*.c")
diff --git a/tests/actions/addon/custom-toolchain/addon.lua b/tests/actions/addon/custom-toolchain/addon.lua
new file mode 100644
index 000000000..8537b3272
--- /dev/null
+++ b/tests/actions/addon/custom-toolchain/addon.lua
@@ -0,0 +1,6 @@
+addon("custom-toolchain")
+ set_description("the addon which provides a custom toolchain")
+ set_sourcedir("src")
+ -- the tool modules are imported with their plain names by the internal calls,
+ -- e.g. find_tool("mycl6x"), import("core.tools.mycl6x")
+ add_globalmodules("core.tools.mycl6x", "detect.tools.find_mycl6x")
diff --git a/tests/actions/addon/custom-toolchain/src/modules/core/tools/mycl6x.lua b/tests/actions/addon/custom-toolchain/src/modules/core/tools/mycl6x.lua
new file mode 100644
index 000000000..b58743d4b
--- /dev/null
+++ b/tests/actions/addon/custom-toolchain/src/modules/core/tools/mycl6x.lua
@@ -0,0 +1,7 @@
+-- our compiler behaves like gcc, it's just a wrapper of the host one
+inherit("core.tools.gcc")
+
+-- the marker of this module, the tests use it to check that it comes from the addon
+function greeting()
+ return "hello from the custom toolchain addon"
+end
diff --git a/tests/actions/addon/custom-toolchain/src/modules/detect/tools/find_mycl6x.lua b/tests/actions/addon/custom-toolchain/src/modules/detect/tools/find_mycl6x.lua
new file mode 100644
index 000000000..443690792
--- /dev/null
+++ b/tests/actions/addon/custom-toolchain/src/modules/detect/tools/find_mycl6x.lua
@@ -0,0 +1,15 @@
+import("lib.detect.find_program")
+
+-- find the compiler of the my-c6000 toolchain
+--
+-- @note it's just the host compiler, we only need a real program to build with,
+-- the point of this test is that this finder itself comes from the addon
+--
+function main(opt)
+ for _, name in ipairs({"gcc", "clang", "cc"}) do
+ local program = find_program(name, opt)
+ if program then
+ return {program = program}
+ end
+ end
+end
diff --git a/tests/actions/addon/custom-toolchain/src/toolchains/my-c6000/xmake.lua b/tests/actions/addon/custom-toolchain/src/toolchains/my-c6000/xmake.lua
new file mode 100644
index 000000000..9b023bac7
--- /dev/null
+++ b/tests/actions/addon/custom-toolchain/src/toolchains/my-c6000/xmake.lua
@@ -0,0 +1,19 @@
+-- a custom toolchain distributed as an addon
+--
+-- @see tests/apis/custom_toolchain for the same toolchain maintained inside a project,
+-- distributing it as an addon is the recommended way
+--
+toolchain("my-c6000")
+ set_kind("standalone")
+ set_description("the custom toolchain of the tests")
+
+ set_toolset("cc", "mycl6x")
+ set_toolset("ld", "mycl6x")
+
+ on_check(function (toolchain)
+ return import("lib.detect.find_tool")("mycl6x")
+ end)
+
+ on_load(function (toolchain)
+ toolchain:add("cxflags", "-DMY_C6000")
+ end)
diff --git a/tests/actions/addon/demo-addon-dep/addon.lua b/tests/actions/addon/demo-addon-dep/addon.lua
deleted file mode 100644
index 2cc460768..000000000
--- a/tests/actions/addon/demo-addon-dep/addon.lua
+++ /dev/null
@@ -1,3 +0,0 @@
-addon("demo-addon-dep")
- set_description("the demo addon which depends on the other addon")
- add_deps("demo-addon")
diff --git a/tests/actions/addon/demo-addon-dep/rules/dep/xmake.lua b/tests/actions/addon/demo-addon-dep/rules/dep/xmake.lua
deleted file mode 100644
index be4805e92..000000000
--- a/tests/actions/addon/demo-addon-dep/rules/dep/xmake.lua
+++ /dev/null
@@ -1,10 +0,0 @@
-rule("dep")
-
- -- depend on a rule of the other addon
- add_deps("@addon/demo-addon/base")
-
- on_load(function (target)
- -- import a module of the other addon
- import("@addon.demo-addon.greeting")
- print("demo-addon-dep: " .. greeting("dep"))
- end)
diff --git a/tests/actions/addon/demo-addon/addon.lua b/tests/actions/addon/demo-addon/addon.lua
deleted file mode 100644
index 546c6a7aa..000000000
--- a/tests/actions/addon/demo-addon/addon.lua
+++ /dev/null
@@ -1,3 +0,0 @@
-addon("demo-addon")
- set_description("the demo addon of the tests")
- set_sourcedir("src")
diff --git a/tests/actions/addon/demo-addon/src/includes/check/xmake.lua b/tests/actions/addon/demo-addon/src/includes/check/xmake.lua
deleted file mode 100644
index 3ba656e64..000000000
--- a/tests/actions/addon/demo-addon/src/includes/check/xmake.lua
+++ /dev/null
@@ -1,7 +0,0 @@
-print("demo-addon: includes check is loaded")
-
-option("demo_addon_option")
- set_default(true)
- set_showmenu(true)
- set_description("An option provided by the demo addon.")
-option_end()
diff --git a/tests/actions/addon/demo-addon/src/modules/greeting.lua b/tests/actions/addon/demo-addon/src/modules/greeting.lua
deleted file mode 100644
index 9adbc01ce..000000000
--- a/tests/actions/addon/demo-addon/src/modules/greeting.lua
+++ /dev/null
@@ -1,3 +0,0 @@
-function main(who)
- return "hello from demo-addon: " .. (who or "world")
-end
diff --git a/tests/actions/addon/demo-addon/src/plugins/demo_hello/main.lua b/tests/actions/addon/demo-addon/src/plugins/demo_hello/main.lua
deleted file mode 100644
index c4bf7951c..000000000
--- a/tests/actions/addon/demo-addon/src/plugins/demo_hello/main.lua
+++ /dev/null
@@ -1,6 +0,0 @@
-import("core.base.option")
-import("@self.greeting")
-
-function main()
- print(greeting(option.get("name")))
-end
diff --git a/tests/actions/addon/demo-addon/src/plugins/demo_hello/xmake.lua b/tests/actions/addon/demo-addon/src/plugins/demo_hello/xmake.lua
deleted file mode 100644
index 8908b23a1..000000000
--- a/tests/actions/addon/demo-addon/src/plugins/demo_hello/xmake.lua
+++ /dev/null
@@ -1,10 +0,0 @@
-task("demo_hello")
- set_category("plugin")
- on_run("main")
- set_menu {
- usage = "xmake demo_hello [options]",
- description = "Say hello from the demo addon.",
- options = {
- {'n', "name", "kv", nil, "Set the name to say hello to."}
- }
- }
diff --git a/tests/actions/addon/demo-addon/src/rules/app/xmake.lua b/tests/actions/addon/demo-addon/src/rules/app/xmake.lua
deleted file mode 100644
index b6236bc28..000000000
--- a/tests/actions/addon/demo-addon/src/rules/app/xmake.lua
+++ /dev/null
@@ -1,9 +0,0 @@
-rule("app")
- add_deps("@self/base")
- on_load(function (target)
- import("@self.greeting")
- import("core.package.addon")
- -- the addon code should never hardcode its own name, it can always ask for it
- local addonname = addon.owner()
- print("demo-addon: rule app is loaded by the addon(%s), %s", addonname, greeting(target:name()))
- end)
diff --git a/tests/actions/addon/demo-addon/src/rules/base/xmake.lua b/tests/actions/addon/demo-addon/src/rules/base/xmake.lua
deleted file mode 100644
index 0417c2361..000000000
--- a/tests/actions/addon/demo-addon/src/rules/base/xmake.lua
+++ /dev/null
@@ -1,4 +0,0 @@
-rule("base")
- on_load(function (target)
- print("demo-addon: rule base is loaded")
- end)
diff --git a/tests/actions/addon/demo-addon/src/templates/c/demoaddon/hello/src/main.c b/tests/actions/addon/demo-addon/src/templates/c/demoaddon/hello/src/main.c
deleted file mode 100644
index 9b130982d..000000000
--- a/tests/actions/addon/demo-addon/src/templates/c/demoaddon/hello/src/main.c
+++ /dev/null
@@ -1,3 +0,0 @@
-int main(int argc, char** argv) {
- return 0;
-}
diff --git a/tests/actions/addon/demo-addon/src/toolchains/demo/xmake.lua b/tests/actions/addon/demo-addon/src/toolchains/demo/xmake.lua
deleted file mode 100644
index 701a72e29..000000000
--- a/tests/actions/addon/demo-addon/src/toolchains/demo/xmake.lua
+++ /dev/null
@@ -1,6 +0,0 @@
-toolchain("demo")
- set_kind("standalone")
- set_description("the demo toolchain of the demo addon")
- on_load(function (toolchain)
- toolchain:set("toolset", "cc", "gcc")
- end)
diff --git a/tests/actions/addon/demo-addon/tests/hello_test.lua b/tests/actions/addon/demo-addon/tests/hello_test.lua
deleted file mode 100644
index 1fcb312de..000000000
--- a/tests/actions/addon/demo-addon/tests/hello_test.lua
+++ /dev/null
@@ -1 +0,0 @@
--- our own test file, it should not be installed
diff --git a/tests/actions/addon/test.lua b/tests/actions/addon/test.lua
index a72cc4236..3542c907b 100644
--- a/tests/actions/addon/test.lua
+++ b/tests/actions/addon/test.lua
@@ -1,20 +1,14 @@
import("core.base.global")
+import("lib.detect.find_program")
--- the addon fixtures, @see tests/actions/addon/demo-addon
+-- the addon fixtures, each of them provides one kind of payload only
--
--- demo-addon: the `src` layout, it provides all the payload kinds
--- demo-addon-dep: the plain layout (payloads at the root), it depends on demo-addon
-local ADDON = "demo-addon"
-local ADDON_DEP = "demo-addon-dep"
+-- @note they are independent from each other, @see tests/actions/addon/custom-*
function _addondir(name)
return path.join(os.scriptdir(), name)
end
-function _installdir(name)
- return path.join(global.directory(), "addons", name, "latest")
-end
-
function _remove(name)
try { function () os.runv("xmake", {"addon", "--remove", "--force", name}) end }
end
@@ -44,25 +38,13 @@ function _with_addons(names, func)
}
end
--- create a temporary repository which indexes the addon fixtures, and register it
+-- create a temporary repository which indexes the given addon fixtures, and register it
--
-- @note the recipes only point at the fixtures with `set_sourcedir`, we need not generate any payload
-function _with_repo(func)
+function _with_repo(recipes, func)
local suffix = path.filename(os.tmpfile()):gsub("[^%w]", "")
local reponame = "addon-test-repo-" .. suffix
local repodir = os.tmpfile() .. ".addon-repo"
- local recipes = {
- -- the addon itself, its manifest sets the payload root directory, e.g. set_sourcedir("src")
- [ADDON] = ("set_sourcedir(%q)"):format(_addondir(ADDON)),
- -- the addon which depends on the addon above
- [ADDON_DEP] = ("set_sourcedir(%q)\n add_deps(%q, {kind = \"addon\"})"):format(_addondir(ADDON_DEP), ADDON),
- -- the addon which provides the same plugin and template names as demo-addon
- --
- -- @note it points at the payloads directly, so it has no manifest and no name conflict
- ["demo-addon-clone"] = ("set_sourcedir(%q)"):format(path.join(_addondir(ADDON), "src")),
- -- the addon whose package name does not match the name in its manifest
- ["demo-addon-badname"] = ("set_sourcedir(%q)"):format(_addondir(ADDON))
- }
for name, body in pairs(recipes) do
io.writefile(path.join(repodir, "addons", name:sub(1, 1), name, "xmake.lua"),
("package(%q)\n set_kind(\"addon\")\n set_description(\"the addon fixture of the tests\")\n %s\n"):format(name, body))
@@ -99,16 +81,20 @@ function _with_repo(func)
}
end
--- run `xmake config` in a temporary project and return its output
-function _config_project(content)
+-- run the given command in a temporary project and return its output
+function _run_project(content, argv, opt)
+ opt = opt or {}
local projectdir = os.tmpfile() .. ".addon-project"
os.tryrm(projectdir)
io.writefile(path.join(projectdir, "xmake.lua"), content)
+ for file, filecontent in pairs(opt.files or {}) do
+ io.writefile(path.join(projectdir, file), filecontent)
+ end
local oldir = os.cd(projectdir)
local out, errors
try
{
- function () out = os.iorunv("xmake", {"config", "-y"}) end,
+ function () out = os.iorunv("xmake", argv) end,
catch { function (e) errors = e end },
finally
{
@@ -124,123 +110,183 @@ function _config_project(content)
return out
end
+function _config_project(content)
+ return _run_project(content, {"config", "-y"})
+end
+
-- only the payloads should be installed, our own files should not
function test_install(t)
- _with_addons({ADDON}, function ()
- local installdir = _installdir(ADDON)
- for _, payloaddir in ipairs({"plugins", "rules", "toolchains", "modules", "includes", "templates"}) do
- t:require(os.isdir(path.join(installdir, payloaddir)))
- end
- for _, ourfile in ipairs({"src", "tests"}) do
+ _with_addons({"custom-toolchain"}, function ()
+
+ -- it has the `src` layout, e.g. set_sourcedir("src")
+ local installdir = path.join(global.directory(), "addons", "custom-toolchain", "latest")
+ t:require(os.isdir(path.join(installdir, "toolchains")))
+ t:require(os.isdir(path.join(installdir, "modules")))
+ for _, ourfile in ipairs({"src", "addon.lua"}) do
t:require_not(os.exists(path.join(installdir, ourfile)))
end
+
-- the addon describes itself, we should get its description from the manifest
- t:require(os.iorunv("xmake", {"addon", "--list"}):find("the demo addon of the tests", 1, true))
+ t:require(os.iorunv("xmake", {"addon", "--list"}):find("the addon which provides a custom toolchain", 1, true))
+ end)
+end
+
+-- an addon can provide a plugin, e.g. xmake hello_addon
+function test_plugin(t)
+ _with_addons({"custom-plugin"}, function ()
+ t:require(os.iorunv("xmake", {"hello_addon", "-n", "xmake"}):find("hello from custom-plugin: xmake", 1, true))
end)
-- it should not be runnable after removing it
- t:require_not(try { function () os.runv("xmake", {"demo_hello"}); return true end })
+ t:require_not(try { function () os.runv("xmake", {"hello_addon"}); return true end })
end
--- all the payload kinds should be activated
-function test_payloads(t)
- _with_addons({ADDON}, function ()
+-- an addon can provide a rule, e.g. add_rules("@addon/custom-rule/hello")
+function test_rule(t)
+ local projectfile = [[
+target("test")
+ set_kind("phony")
+ add_rules("@addon/custom-rule/hello")
+]]
+ _with_addons({"custom-rule"}, function ()
+ -- the rule imports a module of its own addon with `@self`
+ t:require(_config_project(projectfile):find("custom-rule: hello from custom-rule: test", 1, true))
+ end)
- -- the plugin imports a module of its own addon with `@self`
- t:require(os.iorunv("xmake", {"demo_hello", "-n", "xmake"}):find("hello from demo-addon: xmake", 1, true))
+ -- it should fail if the addon which provides it is not installed
+ t:require_not(try { function () _config_project(projectfile); return true end })
+end
- -- the module can be imported with the addon name
- local script = "import(\"@addon.demo-addon.greeting\"); print(greeting(\"module\"))"
- t:require(os.iorunv("xmake", {"lua", "-c", script}):find("hello from demo-addon: module", 1, true))
+-- an addon can provide a module, e.g. import("@addon.custom-module.greeting")
+function test_module(t)
+ _with_addons({"custom-module"}, function ()
+ local script = "import(\"@addon.custom-module.greeting\"); print(greeting(\"xmake\"))"
+ t:require(os.iorunv("xmake", {"lua", "-c", script}):find("hello from custom-module: xmake", 1, true))
- -- the toolchain can be loaded with the addon name
- local script2 = "import(\"core.tool.toolchain\"); print(toolchain.load(\"@addon/demo-addon/demo\"):get(\"description\"))"
- t:require(os.iorunv("xmake", {"lua", "-c", script2}):find("the demo toolchain", 1, true))
+ -- the addon modules are namespaced, they cannot be imported with their plain names
+ t:require_not(try { function () os.runv("xmake", {"lua", "-c", "import(\"greeting\")"}); return true end })
+ end)
+end
+
+-- an addon can provide an includes file, e.g. includes("@addon/custom-include/check")
+function test_include(t)
+ _with_addons({"custom-include"}, function ()
+ local out = _config_project([[
+includes("@addon/custom-include/check")
+target("test")
+ set_kind("phony")
+]])
+ t:require(out:find("custom-include: includes check is loaded", 1, true))
+ end)
+end
+
+-- an addon can provide a project template, e.g. xmake create -t customaddon.hello
+function test_template(t)
+ _with_addons({"custom-template"}, function ()
+ t:require(os.iorunv("xmake", {"create", "--list", "-l", "c"}):find("customaddon.hello", 1, true))
- -- the template should be listed and can create a project
- t:require(os.iorunv("xmake", {"create", "--list", "-l", "c"}):find("demoaddon.hello", 1, true))
local projectdir = os.tmpfile() .. ".addon-project"
os.tryrm(projectdir)
- os.runv("xmake", {"create", "-l", "c", "-t", "demoaddon.hello", "-P", projectdir})
+ os.runv("xmake", {"create", "-l", "c", "-t", "customaddon.hello", "-P", projectdir})
t:require(os.isfile(path.join(projectdir, "src", "main.c")))
os.tryrm(projectdir)
+ end)
+end
- -- the includes and the rules should work in a project, the app rule depends on
- -- the other rule of the same addon with `add_deps("@self/base")`
- local out = _config_project([[
-includes("@addon/demo-addon/check")
+-- an addon can provide a toolchain and its tool modules
+--
+-- @see tests/apis/custom_toolchain for the same toolchain maintained inside a project
+function test_toolchain(t)
+ _with_addons({"custom-toolchain"}, function ()
+
+ -- the toolchain can be loaded with the addon name
+ local script = "import(\"core.tool.toolchain\"); print(toolchain.load(\"@addon/custom-toolchain/my-c6000\"):get(\"description\"))"
+ t:require(os.iorunv("xmake", {"lua", "-c", script}):find("the custom toolchain of the tests", 1, true))
+
+ -- its tool modules are exported by the manifest, so the internal calls can import them
+ -- with their plain names, e.g. add_globalmodules("core.tools.mycl6x")
+ local script2 = "import(\"core.tools.mycl6x\"); print(mycl6x.greeting()); " ..
+ "import(\"lib.detect.find_tool\"); assert(find_tool(\"mycl6x\"), \"mycl6x not found!\")"
+ t:require(os.iorunv("xmake", {"lua", "-c", script2}):find("hello from the custom toolchain addon", 1, true))
+
+ -- and a project can build with it
+ --
+ -- @note its compiler is just the host one, so we can only build with it if there is one
+ if find_program("gcc") or find_program("clang") or find_program("cc") then
+ _run_project([[
target("test")
- set_kind("phony")
- add_rules("@addon/demo-addon/app")
-]])
- t:require(out:find("demo-addon: includes check is loaded", 1, true))
- t:require(out:find("demo-addon: rule base is loaded", 1, true))
- t:require(out:find("demo-addon: rule app is loaded by the addon(demo-addon), hello from demo-addon: test", 1, true))
+ set_kind("binary")
+ set_toolchains("@addon/custom-toolchain/my-c6000")
+ add_files("src/main.c")
+]], {"build", "-y"}, {files = {["src/main.c"] = "int main(int argc, char** argv) { return 0; }\n"}})
+ end
end)
end
--- install the addons from a repository, by plain name and by repo@name
+-- the addons can be installed from a repository, by plain name and by repo@name
function test_install_from_repo(t)
- _with_repo(function (reponame)
- os.runv("xmake", {"addon", "--install", "-y", ADDON})
- t:require(os.iorunv("xmake", {"demo_hello"}):find("hello from demo-addon", 1, true))
+ local recipes = {["custom-plugin"] = ("set_sourcedir(%q)"):format(_addondir("custom-plugin"))}
+ _with_repo(recipes, function (reponame)
+ os.runv("xmake", {"addon", "--install", "-y", "custom-plugin"})
+ t:require(os.iorunv("xmake", {"hello_addon"}):find("hello from custom-plugin", 1, true))
- os.runv("xmake", {"addon", "--remove", ADDON})
- os.runv("xmake", {"addon", "--install", "-y", reponame .. "@" .. ADDON})
- t:require(os.iorunv("xmake", {"demo_hello"}):find("hello from demo-addon", 1, true))
+ os.runv("xmake", {"addon", "--remove", "custom-plugin"})
+ os.runv("xmake", {"addon", "--install", "-y", reponame .. "@custom-plugin"})
+ t:require(os.iorunv("xmake", {"hello_addon"}):find("hello from custom-plugin", 1, true))
-- it should be searchable, and the addons should not be found by the package search
- t:require(os.iorunv("xmake", {"addon", "--search", ADDON}):find(ADDON, 1, true))
- t:require_not(os.iorunv("xrepo", {"search", ADDON}):find(ADDON, 1, true))
+ t:require(os.iorunv("xmake", {"addon", "--search", "custom-plugin"}):find("custom-plugin", 1, true))
+ t:require_not(os.iorunv("xrepo", {"search", "custom-plugin"}):find("custom-plugin", 1, true))
end)
end
--- an addon can depend on the other addons, and use their rules and modules
+-- an addon can depend on the other addons, they are installed and activated together
function test_addon_deps(t)
- _with_repo(function ()
-
- -- installing it should install and activate its addon dependency
- os.runv("xmake", {"addon", "--install", "-y", ADDON_DEP})
- t:require(os.iorunv("xmake", {"addon", "--list"}):find(ADDON, 1, true))
+ local recipes = {
+ ["custom-module"] = ("set_sourcedir(%q)"):format(_addondir("custom-module")),
+ ["custom-plugin"] = ("set_sourcedir(%q)\n add_deps(\"custom-module\", {kind = \"addon\"})"):format(_addondir("custom-plugin"))
+ }
+ _with_repo(recipes, function ()
+ os.runv("xmake", {"addon", "--install", "-y", "custom-plugin"})
+ local out = os.iorunv("xmake", {"addon", "--list"})
+ t:require(out:find("custom-plugin", 1, true))
+ t:require(out:find("custom-module", 1, true))
-- we cannot remove the dependency, it's depended on by the other addon
- t:require_not(try { function () os.runv("xmake", {"addon", "--remove", ADDON}); return true end })
-
- -- its rule depends on the rule of the other addon and imports its module
- local out = _config_project([[
-target("test")
- set_kind("phony")
- add_rules("@addon/demo-addon-dep/dep")
-]])
- t:require(out:find("demo-addon: rule base is loaded", 1, true))
- t:require(out:find("demo-addon-dep: hello from demo-addon: dep", 1, true))
+ t:require_not(try { function () os.runv("xmake", {"addon", "--remove", "custom-module"}); return true end })
end)
end
-- the plugins and the templates are not namespaced, the conflicts should be rejected when installing
function test_install_conflicts(t)
- _with_repo(function ()
- os.runv("xmake", {"addon", "--install", "-y", ADDON})
+ _with_addons({"custom-plugin"}, function ()
- -- this addon provides the same plugin and template names
- t:require_not(try { function () os.runv("xmake", {"addon", "--install", "-y", "demo-addon-clone"}); return true end })
+ -- this addon provides the same plugin name
+ local clonedir = os.tmpfile() .. ".addon-clone"
+ os.tryrm(clonedir)
+ io.writefile(path.join(clonedir, "addon.lua"), "addon(\"custom-plugin-clone\")\n")
+ io.writefile(path.join(clonedir, "plugins", "hello_addon", "xmake.lua"),
+ "task(\"hello_addon\")\n set_category(\"plugin\")\n set_menu {}\n on_run(function () end)\n")
+ t:require_not(try { function () os.runv("xmake", {"addon", "--install", "-y", clonedir}); return true end })
+ os.tryrm(clonedir)
-- and the other commands should still work
- t:require(os.iorunv("xmake", {"addon", "--list"}):find(ADDON, 1, true))
- t:require(os.iorunv("xmake", {"demo_hello"}):find("hello from demo-addon", 1, true))
+ t:require(os.iorunv("xmake", {"hello_addon"}):find("hello from custom-plugin", 1, true))
end)
end
-- the invalid installs should fail, and the `addon` name is reserved for the addon references
function test_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 })
-- the addon name in its manifest must match the package name which distributes it
- _with_repo(function ()
- t:require_not(try { function () os.runv("xmake", {"addon", "--install", "-y", "demo-addon-badname"}); return true end })
+ local recipes = {["custom-plugin-badname"] = ("set_sourcedir(%q)"):format(_addondir("custom-plugin"))}
+ _with_repo(recipes, function ()
+ t:require_not(try { function () os.runv("xmake", {"addon", "--install", "-y", "custom-plugin-badname"}); return true end })
end)
- t:require_not(try { function () os.runv("xmake", {"addon", "--install", "-y", "somerepo@.."}); return true end })
+ -- the `addon` name is reserved
t:require_not(try { function () _config_project([[
add_requires("addon")
target("test")
diff --git a/xmake/actions/addon/main.lua b/xmake/actions/addon/main.lua
index 878b1712d..481b9334b 100644
--- a/xmake/actions/addon/main.lua
+++ b/xmake/actions/addon/main.lua
@@ -118,12 +118,19 @@ function _install_from_local(dir, name)
{
function ()
addon.register(name, LOCALVERSION, manifest and {
- description = manifest.description, deps = #manifest.deps > 0 and manifest.deps or nil})
+ description = manifest.description,
+ deps = #manifest.deps > 0 and manifest.deps or nil,
+ globalmodules = #manifest.globalmodules > 0 and manifest.globalmodules or nil})
end,
catch
{
function (errors)
os.tryrm(dstdir)
+ -- we need to remove the addon directory too if no other version is installed
+ local addondir = path.directory(dstdir)
+ if #os.filedirs(path.join(addondir, "*")) == 0 then
+ os.tryrm(addondir)
+ end
raise(errors)
end
}
diff --git a/xmake/core/package/addon.lua b/xmake/core/package/addon.lua
index e5b03b959..1f116bcd4 100644
--- a/xmake/core/package/addon.lua
+++ b/xmake/core/package/addon.lua
@@ -154,16 +154,25 @@ end
-- @return the errors if there are some conflicts
--
function addon._check_conflicts(dirname, addoninfo)
- for _, kind in ipairs({"plugins", "templates"}) do
+ local kindnames = {plugins = "plugin", templates = "template", globalmodules = "global module"}
+ for _, kind in ipairs({"plugins", "templates", "globalmodules"}) do
for _, name in ipairs(addoninfo[kind] or {}) do
for otherdirname, otheraddoninfo in pairs(addon.addons()) do
if otherdirname ~= dirname and table.contains(otheraddoninfo[kind] or {}, name) then
return string.format("%s(%s) conflicts, it has been provided by the addon(%s)!\nplease remove one of them, e.g. xmake addon --remove %s",
- kind == "plugins" and "plugin" or "template", name, otherdirname, otherdirname)
+ kindnames[kind], name, otherdirname, otherdirname)
end
end
end
end
+
+ -- the global modules can also conflict with the builtin ones
+ for _, name in ipairs(addoninfo.globalmodules or {}) do
+ local modulefile = path.join(os.programdir(), "modules", (name:gsub("%.", "/")) .. ".lua")
+ if os.isfile(modulefile) then
+ return string.format("global module(%s) conflicts, it has been provided by xmake!\nplease rename it in the addon manifest.", name)
+ end
+ end
end
-- get the addons which depend on the given addon
@@ -203,6 +212,7 @@ function addon.apis()
, "addon.set_sourcedir"
-- addon.add_xxx
, "addon.add_deps"
+ , "addon.add_globalmodules"
}
}
end
@@ -252,7 +262,8 @@ function addon.manifest(sourcedir)
homepage = addoninfo:get("homepage"),
license = addoninfo:get("license"),
sourcedir = addoninfo:get("sourcedir"),
- deps = table.wrap(addoninfo:get("deps"))}
+ deps = table.wrap(addoninfo:get("deps")),
+ globalmodules = table.wrap(addoninfo:get("globalmodules"))}
end
if not manifest then
return nil, string.format("%s: no addon() scope found!", manifestfile)
@@ -422,6 +433,28 @@ function addon.addondir(name, version)
return path.join(addon.installdir(), dirname, version)
end
+-- get the modules which the installed addons export as the global modules
+--
+-- they are declared in the addon manifest, e.g. add_globalmodules("core.tools.esptool"),
+-- so that they can be imported with their plain names by the internal calls,
+-- e.g. import("core.tools.esptool"), find_tool("esptool")
+--
+-- @return the modules table, e.g. {["core.tools.esptool"] = "~/.xmake/addons/esp32/v1.0.0/modules"}
+--
+function addon.globalmodules()
+ local globalmodules = addon._GLOBALMODULES
+ if globalmodules == nil then
+ globalmodules = {}
+ for dirname, addoninfo in pairs(addon.addons()) do
+ for _, name in ipairs(addoninfo.globalmodules or {}) do
+ globalmodules[name] = path.join(addon.installdir(), dirname, addoninfo.version, "modules")
+ end
+ end
+ addon._GLOBALMODULES = globalmodules
+ end
+ return globalmodules
+end
+
-- get the payload directories of the given kind from all installed addons
--
-- @param kind the payload kind, e.g. "plugins", "rules"
@@ -560,6 +593,7 @@ function addon.register(name, version, opt)
-- recorded whenever this addon has one, so that the repositories can
-- check that the manifest and the package recipe are kept in sync
manifest_deps = opt.manifest_deps,
+ globalmodules = opt.globalmodules,
payloads = addon.payloads_of(addondir),
plugins = addon._plugins_of(addondir),
templates = addon._templates_of(addondir)}
@@ -665,6 +699,7 @@ function addon.clear()
end
addon._ADDONS = {}
addon._MANIFESTS = nil
+ addon._GLOBALMODULES = nil
end
-- return module
diff --git a/xmake/core/sandbox/modules/import/core/sandbox/module.lua b/xmake/core/sandbox/modules/import/core/sandbox/module.lua
index 3fd2d0fe6..4cf419b44 100644
--- a/xmake/core/sandbox/modules/import/core/sandbox/module.lua
+++ b/xmake/core/sandbox/modules/import/core/sandbox/module.lua
@@ -441,28 +441,6 @@ function core_sandbox_module.coredir()
return path.join(os.programdir(), "core/sandbox/modules/import")
end
--- get the module directories for the given addon reference
---
--- they are only used for the addon modules,
--- e.g. import("@addon.esp32.sdkconfig"), import("@self.sdkconfig")
---
-function core_sandbox_module.addon_directories(modulesdir)
- local moduledirs = {modulesdir}
-
- -- add the modules of the addon toolchains, e.g. <addondir>/toolchains/<name>/modules
- -- so that a custom toolchain can bundle its tool modules together
- local toolchainsdir = path.join(path.directory(modulesdir), "toolchains")
- if os.isdir(toolchainsdir) then
- for _, toolchaindir in ipairs(os.dirs(path.join(toolchainsdir, "*"))) do
- local dir = path.join(toolchaindir, "modules")
- if os.isdir(dir) then
- table.insert(moduledirs, dir)
- end
- end
- end
- return moduledirs
-end
-
-- add module directories
function core_sandbox_module.add_directories(...)
local moduledirs = core_sandbox_module.directories()
@@ -567,9 +545,22 @@ function core_sandbox_module.import(name, opt)
-- init module directories (disable local packages?)
local modules_directories
if addon_modulesdir then
- modules_directories = core_sandbox_module.addon_directories(addon_modulesdir)
+ -- the addon modules are always resolved from the addon `modules` directory only,
+ -- e.g. import("@addon.esp32.sdkconfig"), import("@self.sdkconfig")
+ modules_directories = {addon_modulesdir}
else
modules_directories = (opt.nolocal or not rootdir) and core_sandbox_module.directories() or table.join(rootdir, core_sandbox_module.directories())
+
+ -- an addon can export some modules as the global modules, e.g. add_globalmodules("core.tools.esptool"),
+ -- so that the internal calls can import them with their plain names, e.g. import("core.tools.esptool")
+ --
+ -- @note we only accept the declared names, the other modules of this addon are
+ -- still private and can only be imported with `@addon.`/`@self.`
+ --
+ local globalmodulesdir = addon.globalmodules()[name]
+ if globalmodulesdir then
+ table.insert(modules_directories, rootdir and 2 or 1, globalmodulesdir)
+ end
end
-- load module
diff --git a/xmake/modules/private/action/require/impl/actions/install.lua b/xmake/modules/private/action/require/impl/actions/install.lua
index 8bc51f4b3..c9e09763e 100644
--- a/xmake/modules/private/action/require/impl/actions/install.lua
+++ b/xmake/modules/private/action/require/impl/actions/install.lua
@@ -529,7 +529,8 @@ function main(package)
end
addon.register(package:name(), package:version_str() or "latest",
{description = description, deps = deps,
- manifest_deps = manifest and manifest.deps or nil})
+ manifest_deps = manifest and manifest.deps or nil,
+ globalmodules = manifest and #manifest.globalmodules > 0 and manifest.globalmodules or nil})
end
installed_now = true
end