diff options
| author | ruki <[email protected]> | 2026-08-09 21:54:48 +0800 |
|---|---|---|
| committer | ruki <[email protected]> | 2026-08-09 21:54:48 +0800 |
| commit | 1fbaa53ea58f6359b3dcd277c9794f81bd4a7bcf (patch) | |
| tree | 97167f1076bd30bfb3cd5bfe26210601d6c271b4 | |
| parent | 7caece0cb3ee8ab207130edc17421d604610e686 (diff) | |
add demo addon
15 files changed, 192 insertions, 29 deletions
diff --git a/tests/actions/addon/demo-addon/README.md b/tests/actions/addon/demo-addon/README.md new file mode 100644 index 000000000..8ee255f64 --- /dev/null +++ b/tests/actions/addon/demo-addon/README.md @@ -0,0 +1,5 @@ +# demo-addon + +A complete addon used by the addon tests, it provides all the payload kinds +and uses the optional `src` layout: only `src/` is installed, this README and +the `tests` directory are ours and must not be installed. diff --git a/tests/actions/addon/demo-addon/src/includes/check/xmake.lua b/tests/actions/addon/demo-addon/src/includes/check/xmake.lua new file mode 100644 index 000000000..3ba656e64 --- /dev/null +++ b/tests/actions/addon/demo-addon/src/includes/check/xmake.lua @@ -0,0 +1,7 @@ +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 new file mode 100644 index 000000000..9adbc01ce --- /dev/null +++ b/tests/actions/addon/demo-addon/src/modules/greeting.lua @@ -0,0 +1,3 @@ +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 new file mode 100644 index 000000000..c4bf7951c --- /dev/null +++ b/tests/actions/addon/demo-addon/src/plugins/demo_hello/main.lua @@ -0,0 +1,6 @@ +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 new file mode 100644 index 000000000..8908b23a1 --- /dev/null +++ b/tests/actions/addon/demo-addon/src/plugins/demo_hello/xmake.lua @@ -0,0 +1,10 @@ +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 new file mode 100644 index 000000000..ff54eb85a --- /dev/null +++ b/tests/actions/addon/demo-addon/src/rules/app/xmake.lua @@ -0,0 +1,6 @@ +rule("app") + add_deps("@self/base") + on_load(function (target) + import("@self.greeting") + print("demo-addon: rule app is loaded, " .. 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 new file mode 100644 index 000000000..0417c2361 --- /dev/null +++ b/tests/actions/addon/demo-addon/src/rules/base/xmake.lua @@ -0,0 +1,4 @@ +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 new file mode 100644 index 000000000..e5a9ccbdf --- /dev/null +++ b/tests/actions/addon/demo-addon/src/templates/c/demoaddon/hello/src/main.c @@ -0,0 +1,4 @@ +int main(int argc, char** argv) +{ + return 0; +} diff --git a/tests/actions/addon/demo-addon/src/templates/c/demoaddon/hello/xmake.lua b/tests/actions/addon/demo-addon/src/templates/c/demoaddon/hello/xmake.lua new file mode 100644 index 000000000..f9ea6e8d2 --- /dev/null +++ b/tests/actions/addon/demo-addon/src/templates/c/demoaddon/hello/xmake.lua @@ -0,0 +1,3 @@ +target("${TARGET_NAME}") + set_kind("binary") + add_files("src/*.c") diff --git a/tests/actions/addon/demo-addon/src/toolchains/demo/xmake.lua b/tests/actions/addon/demo-addon/src/toolchains/demo/xmake.lua new file mode 100644 index 000000000..701a72e29 --- /dev/null +++ b/tests/actions/addon/demo-addon/src/toolchains/demo/xmake.lua @@ -0,0 +1,6 @@ +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 new file mode 100644 index 000000000..1fcb312de --- /dev/null +++ b/tests/actions/addon/demo-addon/tests/hello_test.lua @@ -0,0 +1 @@ +-- our own test file, it should not be installed diff --git a/tests/actions/addon/test.lua b/tests/actions/addon/test.lua index 2be6acf94..1ead7acac 100644 --- a/tests/actions/addon/test.lua +++ b/tests/actions/addon/test.lua @@ -406,6 +406,60 @@ function test_install_conflicts(t) end) end +-- a complete addon with the optional `src` layout, @see tests/actions/addon/demo-addon +-- +-- it provides all the payload kinds and has its own files (README, tests) which must not be installed +function test_demo_addon(t) + local name = "demo-addon" + local addondir = path.join(os.scriptdir(), name) + try + { + function () + os.runv("xmake", {"addon", "--install", addondir}) + + -- only the payloads of the `src` directory should be installed + local installdir = path.join(global.directory(), "addons", name, "latest") + 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", "README.md"}) do + t:require_not(os.exists(path.join(installdir, ourfile))) + 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)) + + -- 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)) + + -- 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 template should be listed + t:require(os.iorunv("xmake", {"create", "--list", "-l", "c"}):find("demoaddon.hello", 1, true)) + + -- the includes and the rules should work in a real project + local out = _config_project([[ +includes("@addon/demo-addon/check") +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, hello from demo-addon: test", 1, true)) + end, + finally + { + function () + try { function () os.runv("xmake", {"addon", "--remove", "--force", name}) end } + end + } + } +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]", "") diff --git a/xmake/actions/addon/main.lua b/xmake/actions/addon/main.lua index c88e6300e..722cdd52d 100644 --- a/xmake/actions/addon/main.lua +++ b/xmake/actions/addon/main.lua @@ -78,11 +78,22 @@ end -- install a single addon from a source directory (as the given name, default to the directory name) function _install_from_local(dir, name) assert(os.isdir(dir), "addon path(%s) not found!", dir) - assert(#addon.payloads_of(dir) > 0, "addon path(%s): no payload directory found, e.g. ${bright}plugins${clear}!", dir) - name = name or path.filename(path.absolute(dir)) + + -- we need to normalize it first, e.g. `/path/to/myaddon/` -> `/path/to/myaddon`, + -- otherwise we cannot get the addon name from the directory + dir = path.normalize(path.absolute(dir)) + + -- we only install the payload directories, the addon repository may have its own files, + -- e.g. tests, ci scripts and documents, and they can also be placed in the `src` subdirectory + local payloadroot = addon.payloadroot(dir) + assert(payloadroot, "addon path(%s): no payload directory found, e.g. ${bright}plugins${clear}!", dir) + + name = name or path.filename(dir) local dstdir = _get_addondir(name, LOCALVERSION) assert(not os.isdir(dstdir), "addon(%s) already exists!", name) - os.vcp(dir, dstdir) + for _, payloaddir in ipairs(addon.payloads_of(payloadroot)) do + os.vcp(path.join(payloadroot, payloaddir), path.join(dstdir, payloaddir)) + end local ok, errors = addon.register(name, LOCALVERSION) if not ok then os.tryrm(dstdir) @@ -127,13 +138,19 @@ function _install_one(name) return end - -- git url or local path + -- local directory + -- + -- @note we need to check it before the git url, `git.asgiturl` also accepts + -- the local paths with a trailing separator, e.g. `/path/to/myaddon/` + if os.isdir(name) then + _install_from_local(name) + return + end + + -- git url if git.asgiturl(name) then _install_from_git(name) return - elseif os.isdir(name) then - _install_from_local(name) - return end -- plain name: try to find it in repositories diff --git a/xmake/core/package/addon.lua b/xmake/core/package/addon.lua index 7ced8a780..8523da8d0 100644 --- a/xmake/core/package/addon.lua +++ b/xmake/core/package/addon.lua @@ -233,6 +233,28 @@ function addon.payloadinfos(kind) return payloadinfos end +-- get the payload root directory of the given addon source directory +-- +-- an addon repository has its own files, e.g. tests, ci scripts and documents, +-- so its payloads can be placed in the `src` subdirectory, and we only install them +-- +-- e.g. +-- esp32-devel/src/{plugins,rules,toolchains,templates} -- with the `src` layout +-- hello-world/{plugins} -- without it, for the simple addons +-- +-- @param sourcedir the addon source directory +-- @return the payload root directory, it will be nil if no payload is found +-- +function addon.payloadroot(sourcedir) + local srcdir = path.join(sourcedir, "src") + if #addon.payloads_of(srcdir) > 0 then + return srcdir + end + if #addon.payloads_of(sourcedir) > 0 then + return sourcedir + end +end + -- get the payload directories of the given addon directory, e.g. {"plugins", "rules"} function addon.payloads_of(addondir) local payloads = {} @@ -250,16 +272,13 @@ end -- function addon.installscript() return function (package) - local installed = false - for _, payloaddir in ipairs(addon.payloaddirs()) do - if os.isdir(payloaddir) then - os.cp(payloaddir, package:installdir()) - installed = true - end - end - if not installed then + local payloadroot = addon.payloadroot(os.curdir()) + if not payloadroot then os.raise("addon(%s): no payload directory found, e.g. plugins!", package:name()) end + for _, payloaddir in ipairs(addon.payloads_of(payloadroot)) do + os.cp(path.join(payloadroot, payloaddir), package:installdir()) + end end end @@ -414,6 +433,19 @@ function addon.remove(name, opt) name, table.concat(parents, ", ")) end end + -- we need to remove the symlinks first, we cannot remove them recursively, + -- otherwise the linked files would be removed too + -- + -- e.g. the user may link the install directory to the addon source directory when developing it + for _, versiondir in ipairs(os.dirs(path.join(installdir, "*"))) do + if os.islink(versiondir) then + os.rmfile(versiondir) + end + end + if os.islink(installdir) then + return os.rmfile(installdir) + end + local ok, errors = os.rm(installdir) if not ok then return false, errors diff --git a/xmake/core/sandbox/modules/import/core/package/addon.lua b/xmake/core/sandbox/modules/import/core/package/addon.lua index 2885bd45f..9591559ed 100644 --- a/xmake/core/sandbox/modules/import/core/package/addon.lua +++ b/xmake/core/sandbox/modules/import/core/package/addon.lua @@ -25,20 +25,25 @@ local sandbox_core_package_addon = sandbox_core_package_addon or {} local addon = require("package/addon") -- inherit some builtin interfaces -sandbox_core_package_addon.installdir = addon.installdir -sandbox_core_package_addon.dirname = addon.dirname -sandbox_core_package_addon.registryfile = addon.registryfile -sandbox_core_package_addon.payloaddirs = addon.payloaddirs -sandbox_core_package_addon.payloads = addon.payloads -sandbox_core_package_addon.payloadinfos = addon.payloadinfos -sandbox_core_package_addon.payloads_of = addon.payloads_of -sandbox_core_package_addon.addons = addon.addons -sandbox_core_package_addon.addondir = addon.addondir -sandbox_core_package_addon.register = addon.register -sandbox_core_package_addon.unregister = addon.unregister -sandbox_core_package_addon.remove = addon.remove -sandbox_core_package_addon.rescan = addon.rescan -sandbox_core_package_addon.clear = addon.clear +sandbox_core_package_addon.installdir = addon.installdir +sandbox_core_package_addon.dirname = addon.dirname +sandbox_core_package_addon.owner = addon.owner +sandbox_core_package_addon.is_reference = addon.is_reference +sandbox_core_package_addon.resolve_reference = addon.resolve_reference +sandbox_core_package_addon.registryfile = addon.registryfile +sandbox_core_package_addon.payloaddirs = addon.payloaddirs +sandbox_core_package_addon.payloads = addon.payloads +sandbox_core_package_addon.payloaddir = addon.payloaddir +sandbox_core_package_addon.payloadinfos = addon.payloadinfos +sandbox_core_package_addon.payloads_of = addon.payloads_of +sandbox_core_package_addon.payloadroot = addon.payloadroot +sandbox_core_package_addon.addons = addon.addons +sandbox_core_package_addon.addondir = addon.addondir +sandbox_core_package_addon.register = addon.register +sandbox_core_package_addon.unregister = addon.unregister +sandbox_core_package_addon.remove = addon.remove +sandbox_core_package_addon.rescan = addon.rescan +sandbox_core_package_addon.clear = addon.clear -- return module return sandbox_core_package_addon |
