From a79cf74f920c088528d66a67a59b3d8eaa1d4f7a Mon Sep 17 00:00:00 2001 From: ruki Date: Fri, 14 Aug 2026 23:28:20 +0800 Subject: autofetch addons --- tests/actions/addon/test.lua | 58 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) (limited to 'tests/actions/addon/test.lua') diff --git a/tests/actions/addon/test.lua b/tests/actions/addon/test.lua index 3542c907b..4db9c0ddf 100644 --- a/tests/actions/addon/test.lua +++ b/tests/actions/addon/test.lua @@ -114,6 +114,32 @@ function _config_project(content) return _run_project(content, {"config", "-y"}) end +-- copy the given fixture project to a temporary directory and run the given function in it +-- +-- @note we cannot run them in place, they would generate the lock and the build files, +-- @see tests/actions/addon/projects +-- +function _with_project(name, func) + local projectdir = os.tmpfile() .. ".addon-project" + os.tryrm(projectdir) + os.mkdir(projectdir) + os.cp(path.join(os.scriptdir(), "projects", name, "*"), projectdir) + local oldir = os.cd(projectdir) + try + { + function () + func(projectdir) + end, + finally + { + function () + os.cd(oldir) + os.tryrm(projectdir) + end + } + } +end + -- only the payloads should be installed, our own files should not function test_install(t) _with_addons({"custom-toolchain"}, function () @@ -223,6 +249,38 @@ target("test") end) end +-- the addons which a project declares in `xmake-addons.lua` are installed automatically +-- +-- @note they must be installed before loading the project, it may use their includes files +function test_autofetch(t) + local recipes = {["custom-include"] = ("set_sourcedir(%q)"):format(_addondir("custom-include"))} + _with_repo(recipes, function () + _remove("custom-include") + _with_project("autofetch", function (projectdir) + + -- it should be installed when loading the project, so that its includes file can be found + t:require(os.iorunv("xmake", {"config", "-y"}):find("custom-include: includes check is loaded", 1, true)) + + -- and it should be locked + local lockfile = path.join(projectdir, "xmake-addons.lock") + t:require(os.isfile(lockfile)) + t:require(io.load(lockfile)["custom-include"] ~= nil) + + -- we should not install it again + t:require_not(os.iorunv("xmake", {"config", "-y"}):find("install custom-include", 1, true)) + end) + end) +end + +-- the addons file only declares the addons, it cannot reference them +function test_autofetch_invalid(t) + for _, name in ipairs({"autofetch-badinclude", "autofetch-badname"}) do + _with_project(name, function () + t:require_not(try { function () os.runv("xmake", {"config", "-y"}); return true end }) + end) + end +end + -- the addons can be installed from a repository, by plain name and by repo@name function test_install_from_repo(t) local recipes = {["custom-plugin"] = ("set_sourcedir(%q)"):format(_addondir("custom-plugin"))} -- cgit v1.3.1 From cd3721d7fbd63387383050b6d345b69822b75ce1 Mon Sep 17 00:00:00 2001 From: ruki Date: Sat, 15 Aug 2026 00:51:42 +0800 Subject: improve project.load --- tests/actions/addon/test.lua | 21 +++++++- xmake/core/project/project.lua | 58 ++++++++++++++++------ .../sandbox/modules/import/core/project/addons.lua | 15 +++--- 3 files changed, 70 insertions(+), 24 deletions(-) (limited to 'tests/actions/addon/test.lua') diff --git a/tests/actions/addon/test.lua b/tests/actions/addon/test.lua index 4db9c0ddf..a14b7b3a5 100644 --- a/tests/actions/addon/test.lua +++ b/tests/actions/addon/test.lua @@ -258,8 +258,11 @@ function test_autofetch(t) _remove("custom-include") _with_project("autofetch", function (projectdir) - -- it should be installed when loading the project, so that its includes file can be found - t:require(os.iorunv("xmake", {"config", "-y"}):find("custom-include: includes check is loaded", 1, true)) + -- it should be installed when loading the project, so that its includes file can be found, + -- and we should tell the user why we install something, it may need to confirm and download + local output = os.iorunv("xmake", {"config", "-y"}) + t:require(output:find("custom-include: includes check is loaded", 1, true)) + t:require(output:find("this project needs the addons", 1, true)) -- and it should be locked local lockfile = path.join(projectdir, "xmake-addons.lock") @@ -272,6 +275,20 @@ function test_autofetch(t) end) end +-- every command builds the option menu, which merges the project tasks in a best-effort way, +-- so the commands which need not the project should never install its addons +function test_autofetch_skipped_for_option_menu(t) + local recipes = {["custom-include"] = ("set_sourcedir(%q)"):format(_addondir("custom-include"))} + _with_repo(recipes, function () + _remove("custom-include") + _with_project("autofetch", function (projectdir) + os.runv("xmake", {"addon", "--list"}) + os.runv("xmake", {"lua", "-c", "print(\"hello\")"}) + t:require_not(os.isfile(path.join(projectdir, "xmake-addons.lock"))) + end) + end) +end + -- the addons file only declares the addons, it cannot reference them function test_autofetch_invalid(t) for _, name in ipairs({"autofetch-badinclude", "autofetch-badname"}) do diff --git a/xmake/core/project/project.lua b/xmake/core/project/project.lua index 2c68976dd..3b975cccd 100644 --- a/xmake/core/project/project.lua +++ b/xmake/core/project/project.lua @@ -275,6 +275,16 @@ function project._do_install_addons() return true end + -- tell the user why we are installing something, it may need to confirm and download, + -- e.g. `xmake --help` in a project directory which declares some addons + utils.cprint("${color.warning}note: ${clear}%s: this project needs the addons(${bright}%s${clear}), installing them ..", + addons.filename(), table.concat(addonsinfo.addons, ", ")) + if baseoption.get("help") then + -- the help menu also shows the options which the addons provide, but the user + -- did not ask for an installation, so we tell them how to skip it + utils.cprint("${dim}we can run it outside of the project directory to skip the installation${clear}") + end + -- @note we run it in a working directory which has no project, @see addon.workdir(), -- otherwise it would load this project again -- @@ -309,19 +319,32 @@ function project._do_install_addons() end -- load the project file -function project._load(force, disable_filter) - - -- has already been loaded? - if project._memcache():get("rootinfo") and not force then - return true - end +-- +-- @param opt the options +-- - force: load the project file again even if it has been loaded +-- - disable_filter: disable the interpreter filter, e.g. `$(plat)` +-- - skip_addons: do not install the addons which this project declares +-- +function project._load(opt) + opt = opt or {} -- install the addons which this project declares in `xmake-addons.lua` first, -- it may use their rules, toolchains and includes files, -- e.g. includes("@addon/esp32-devel/board") - local ok, errors = project._install_addons() - if not ok then - return false, errors + -- + -- @note we need to check it before the cache, the project file may have been loaded + -- already without them, e.g. by the option menu + -- + if not opt.skip_addons then + local ok, errors = project._install_addons() + if not ok then + return false, errors + end + end + + -- has already been loaded? + if project._memcache():get("rootinfo") and not opt.force then + return true end -- enter the project directory @@ -350,13 +373,13 @@ function project._load(force, disable_filter) end -- load the root info of the project - local rootinfo, errors = project._load_scope("root", true, not disable_filter) + local rootinfo, errors = project._load_scope("root", true, not opt.disable_filter) if not rootinfo then return false, errors end -- load the root info of the target - local rootinfo_target, errors = project._load_scope("root.target", true, not disable_filter) + local rootinfo_target, errors = project._load_scope("root.target", true, not opt.disable_filter) if not rootinfo_target then return false, errors end @@ -400,6 +423,11 @@ function project._load_scope(scope_kind, deduplicate, enable_filter) end -- load tasks +-- +-- @note we should not install the addons which this project declares here, the option menu +-- merges the project tasks in a best-effort way and every command builds it, +-- e.g. `xmake lua`, `xmake addon --remove --all`, @see xmake/core/main.lua +-- function project._load_tasks() -- the project file is not found? @@ -408,7 +436,7 @@ function project._load_tasks() end -- load the project file first and disable filter - local ok, errors = project._load(true, true) + local ok, errors = project._load({force = true, disable_filter = true, skip_addons = true}) if not ok then return nil, errors end @@ -489,7 +517,7 @@ function project._load_targets() -- load all requires first and reload the project file to ensure has_package() works for targets local requires = project.required_packages() - local ok, errors = project._load(true) + local ok, errors = project._load({force = true}) if not ok then return nil, errors end @@ -570,7 +598,7 @@ function project._load_options(disable_filter) end -- reload the project file to ensure `if is_plat() then add_packagedirs() end` works - local ok, errors = project._load(true, disable_filter) + local ok, errors = project._load({force = true, disable_filter = disable_filter}) if not ok then return nil, errors end @@ -1218,7 +1246,7 @@ function project.requires_str() if not requires_str then -- reload the project file to handle `has_config()` - local ok, errors = project._load(true) + local ok, errors = project._load({force = true}) if not ok then os.raise(errors) end diff --git a/xmake/core/sandbox/modules/import/core/project/addons.lua b/xmake/core/sandbox/modules/import/core/project/addons.lua index 0c3ad438c..542cbaf4f 100644 --- a/xmake/core/sandbox/modules/import/core/project/addons.lua +++ b/xmake/core/sandbox/modules/import/core/project/addons.lua @@ -26,13 +26,14 @@ local addons = require("project/addons") local raise = require("sandbox/modules/raise") -- inherit some builtin interfaces -sandbox_core_project_addons.file = addons.file -sandbox_core_project_addons.filename = addons.filename -sandbox_core_project_addons.lockfile = addons.lockfile -sandbox_core_project_addons.locked = addons.locked -sandbox_core_project_addons.locked_valid = addons.locked_valid -sandbox_core_project_addons.requirename = addons.requirename -sandbox_core_project_addons.satisfied = addons.satisfied +sandbox_core_project_addons.file = addons.file +sandbox_core_project_addons.filename = addons.filename +sandbox_core_project_addons.lockfile = addons.lockfile +sandbox_core_project_addons.lockfile_version = addons.lockfile_version +sandbox_core_project_addons.locked = addons.locked +sandbox_core_project_addons.locked_valid = addons.locked_valid +sandbox_core_project_addons.requirename = addons.requirename +sandbox_core_project_addons.satisfied = addons.satisfied -- load the declared addons of the given project directory -- -- cgit v1.3.1 From 7028d912fbeabab132e242ec1782645582a75078 Mon Sep 17 00:00:00 2001 From: ruki Date: Sat, 15 Aug 2026 00:58:02 +0800 Subject: fix global modules for addons --- .../src/modules/detect/tools/find_mycl6x.lua | 2 +- .../addon/projects/autofetch-build/src/main.c | 11 +++++ .../projects/autofetch-build/xmake-addons.lua | 2 + .../addon/projects/autofetch-build/xmake.lua | 11 +++++ tests/actions/addon/test.lua | 55 ++++++++++++++++++++-- .../sandbox/modules/import/core/sandbox/module.lua | 8 ++++ 6 files changed, 85 insertions(+), 4 deletions(-) create mode 100644 tests/actions/addon/projects/autofetch-build/src/main.c create mode 100644 tests/actions/addon/projects/autofetch-build/xmake-addons.lua create mode 100644 tests/actions/addon/projects/autofetch-build/xmake.lua (limited to 'tests/actions/addon/test.lua') 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 index 443690792..c0ed37914 100644 --- 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 @@ -9,7 +9,7 @@ function main(opt) for _, name in ipairs({"gcc", "clang", "cc"}) do local program = find_program(name, opt) if program then - return {program = program} + return program end end end diff --git a/tests/actions/addon/projects/autofetch-build/src/main.c b/tests/actions/addon/projects/autofetch-build/src/main.c new file mode 100644 index 000000000..112afe090 --- /dev/null +++ b/tests/actions/addon/projects/autofetch-build/src/main.c @@ -0,0 +1,11 @@ +// the addons must provide the option and the toolchain of this project +#ifndef MYOPTION +# error the option of the custom-include addon is not found! +#endif +#ifndef MY_C6000 +# error the toolchain of the custom-toolchain addon is not used! +#endif + +int main(int argc, char** argv) { + return 0; +} diff --git a/tests/actions/addon/projects/autofetch-build/xmake-addons.lua b/tests/actions/addon/projects/autofetch-build/xmake-addons.lua new file mode 100644 index 000000000..2091e3b7f --- /dev/null +++ b/tests/actions/addon/projects/autofetch-build/xmake-addons.lua @@ -0,0 +1,2 @@ +-- the addons which this project needs, they are installed automatically when we load it +add_addons("custom-include", "custom-rule", "custom-toolchain") diff --git a/tests/actions/addon/projects/autofetch-build/xmake.lua b/tests/actions/addon/projects/autofetch-build/xmake.lua new file mode 100644 index 000000000..bad3f8928 --- /dev/null +++ b/tests/actions/addon/projects/autofetch-build/xmake.lua @@ -0,0 +1,11 @@ +-- the option comes from the includes file of an addon +includes("@addon/custom-include/check") + +target("hello") + set_kind("binary") + add_files("src/main.c") + add_rules("@addon/custom-rule/hello") + set_toolchains("@addon/custom-toolchain/my-c6000") + if has_config("myoption") then + add_defines("MYOPTION") + end diff --git a/tests/actions/addon/test.lua b/tests/actions/addon/test.lua index a14b7b3a5..0963972a3 100644 --- a/tests/actions/addon/test.lua +++ b/tests/actions/addon/test.lua @@ -29,10 +29,14 @@ function _with_addons(names, func) func, finally { - function () + -- @note try() swallows the errors if we do not re-raise them here + function (ok, errors) for _, name in ipairs(names) do _remove(name) end + if not ok then + raise(errors) + end end } } @@ -65,7 +69,8 @@ function _with_repo(recipes, func) end, finally { - function () + -- @note try() swallows the errors if we do not re-raise them here + function (ok, errors) for name, _ in pairs(recipes) do _remove(name) end @@ -76,6 +81,9 @@ function _with_repo(recipes, func) io.save(cachefile, cache) os.tryrm(path.join(global.cachedir(), "quick_search")) os.tryrm(repodir) + if not ok then + raise(errors) + end end } } @@ -132,9 +140,13 @@ function _with_project(name, func) end, finally { - function () + -- @note try() swallows the errors if we do not re-raise them here + function (ok, errors) os.cd(oldir) os.tryrm(projectdir) + if not ok then + raise(errors) + end end } } @@ -289,6 +301,43 @@ function test_autofetch_skipped_for_option_menu(t) end) end +-- a complete project which declares its addons in `xmake-addons.lua` and builds with them, +-- @see tests/actions/addon/projects/autofetch-build +function test_autofetch_build(t) + + -- @note the compiler of the custom toolchain is just the host one, + -- so we can only build it if there is one + if not (find_program("gcc") or find_program("clang") or find_program("cc")) then + return + end + + local names = {"custom-include", "custom-rule", "custom-toolchain"} + local recipes = {} + for _, name in ipairs(names) do + recipes[name] = ("set_sourcedir(%q)"):format(_addondir(name)) + end + _with_repo(recipes, function () + for _, name in ipairs(names) do + _remove(name) + end + _with_project("autofetch-build", function (projectdir) + + -- all of them should be installed when loading the project, and it should build + -- with their includes file, rule and toolchain, @see src/main.c + local output = os.iorunv("xmake", {"build", "-y"}) + t:require(output:find("custom-include: includes check is loaded", 1, true)) + t:require(output:find("custom-rule: hello from custom-rule: hello", 1, true)) + t:require(output:find("build ok", 1, true)) + + -- and all of them should be locked + local lockinfo = io.load(path.join(projectdir, "xmake-addons.lock")) + for _, name in ipairs(names) do + t:require(lockinfo[name] ~= nil) + end + end) + end) +end + -- the addons file only declares the addons, it cannot reference them function test_autofetch_invalid(t) for _, name in ipairs({"autofetch-badinclude", "autofetch-badname"}) do diff --git a/xmake/core/sandbox/modules/import/core/sandbox/module.lua b/xmake/core/sandbox/modules/import/core/sandbox/module.lua index 4cf419b44..294cf7556 100644 --- a/xmake/core/sandbox/modules/import/core/sandbox/module.lua +++ b/xmake/core/sandbox/modules/import/core/sandbox/module.lua @@ -452,6 +452,14 @@ end -- find module function core_sandbox_module.find(name) + + -- an addon can export some modules as the global modules, they are also visible here, + -- e.g. find_toolname() looks for `detect.tools.find_xxx` with it, @see addon.globalmodules() + local globalmodulesdir = addon.globalmodules()[name] + if globalmodulesdir and core_sandbox_module._find(globalmodulesdir, name) then + return true + end + for _, moduledir in ipairs(core_sandbox_module.directories()) do if (core_sandbox_module._find(moduledir, name)) then return true -- cgit v1.3.1 From 045ce8a7a62e38357af0fd250675f279f14beafe Mon Sep 17 00:00:00 2001 From: ruki Date: Sat, 15 Aug 2026 00:10:02 +0800 Subject: fix addon test --- tests/actions/addon/test.lua | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'tests/actions/addon/test.lua') diff --git a/tests/actions/addon/test.lua b/tests/actions/addon/test.lua index 0963972a3..d59ae6f4b 100644 --- a/tests/actions/addon/test.lua +++ b/tests/actions/addon/test.lua @@ -58,6 +58,14 @@ function _with_repo(recipes, func) local cachefile = path.join(global.cachedir(), "repository") local cache = os.isfile(cachefile) and io.load(cachefile) or {} cache.repositories = cache.repositories or {} + + -- a killed test run may leave its temporary repository registered, and a dangling + -- repository breaks every following xrepo command, so we drop them here + for name, dirs in pairs(cache.repositories) do + if name:startswith("addon-test-repo-") and not os.isdir(dirs[1]) then + cache.repositories[name] = nil + end + end cache.repositories[reponame] = {repodir} io.save(cachefile, cache) os.tryrm(path.join(global.cachedir(), "quick_search")) @@ -359,8 +367,12 @@ function test_install_from_repo(t) 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 + -- + -- @note we cannot run the `xrepo` program here, it may not be in the PATH, e.g. on the ci, + -- and `xrepo search` is just a wrapper of it + -- 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)) + t:require_not(os.iorunv("xmake", {"lua", "private.xrepo", "search", "custom-plugin"}):find("custom-plugin", 1, true)) end) end -- cgit v1.3.1