diff options
Diffstat (limited to 'tests')
63 files changed, 1276 insertions, 19 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-override/addon.lua b/tests/actions/addon/custom-override/addon.lua new file mode 100644 index 000000000..3540b51f5 --- /dev/null +++ b/tests/actions/addon/custom-override/addon.lua @@ -0,0 +1,2 @@ +addon("custom-override") + set_description("the addon which takes over a builtin plugin") diff --git a/tests/actions/addon/custom-override/plugins/format/main.lua b/tests/actions/addon/custom-override/plugins/format/main.lua new file mode 100644 index 000000000..92f9b73b8 --- /dev/null +++ b/tests/actions/addon/custom-override/plugins/format/main.lua @@ -0,0 +1,3 @@ +function main() + print("hello from custom-override") +end diff --git a/tests/actions/addon/custom-override/plugins/format/xmake.lua b/tests/actions/addon/custom-override/plugins/format/xmake.lua new file mode 100644 index 000000000..446e24db1 --- /dev/null +++ b/tests/actions/addon/custom-override/plugins/format/xmake.lua @@ -0,0 +1,6 @@ +-- @note `format` is a builtin plugin of xmake, we use it to test that an addon +-- is able to take over a builtin plugin +task("format") + set_category("plugin") + set_menu {usage = "xmake format", description = "Say hello instead of formatting.", options = {}} + on_run("main") diff --git a/tests/actions/addon/custom-package/addon.lua b/tests/actions/addon/custom-package/addon.lua new file mode 100644 index 000000000..56d8300f3 --- /dev/null +++ b/tests/actions/addon/custom-package/addon.lua @@ -0,0 +1,2 @@ +addon("custom-package") + set_description("the addon which provides a custom package") diff --git a/tests/actions/addon/custom-package/includes/packages/src/include/hello_utils.h b/tests/actions/addon/custom-package/includes/packages/src/include/hello_utils.h new file mode 100644 index 000000000..ca969acd2 --- /dev/null +++ b/tests/actions/addon/custom-package/includes/packages/src/include/hello_utils.h @@ -0,0 +1,6 @@ +#pragma once +#include <stdio.h> + +static inline void hello_utils_greeting(void) { + printf("hello from the custom package!\n"); +} diff --git a/tests/actions/addon/custom-package/includes/packages/xmake.lua b/tests/actions/addon/custom-package/includes/packages/xmake.lua new file mode 100644 index 000000000..5a02187fd --- /dev/null +++ b/tests/actions/addon/custom-package/includes/packages/xmake.lua @@ -0,0 +1,13 @@ +-- an addon can ship the package definitions, so that a project only needs +-- includes("@addon/custom-package/packages") and add_requires("hello-utils") +package("hello-utils") + set_kind("library", {headeronly = true}) + set_description("the custom package of the tests") + + -- the sources are shipped by this addon, so it can be installed offline + set_sourcedir(path.join(os.scriptdir(), "src")) + + on_install(function (package) + os.cp("include", package:installdir()) + end) +package_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/custom-template/templates/c/customaddon/hello/xmake.lua b/tests/actions/addon/custom-template/templates/c/customaddon/hello/xmake.lua new file mode 100644 index 000000000..33c36ec7e --- /dev/null +++ b/tests/actions/addon/custom-template/templates/c/customaddon/hello/xmake.lua @@ -0,0 +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..972242c98 --- /dev/null +++ b/tests/actions/addon/custom-toolchain/src/modules/core/tools/mycl6x.lua @@ -0,0 +1,12 @@ +-- the tool module of the my-c6000 toolchain +-- +-- @note our compiler behaves like gcc, it's just a wrapper of the host one, so we only +-- inherit it here, a real one implements the tool interfaces itself, e.g. `init`, +-- `nf_define`, `compargv`, `link`, @see tests/apis/custom_toolchain +-- +inherit("core.tools.gcc") + +-- init it +function init(self) + self:set("cxflags", "-DMY_C6000_TOOL") +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..c0ed37914 --- /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 + 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/projects/autofetch-badname/xmake.lua b/tests/actions/addon/projects/autofetch-badname/xmake.lua new file mode 100644 index 000000000..ffbf6cbe4 --- /dev/null +++ b/tests/actions/addon/projects/autofetch-badname/xmake.lua @@ -0,0 +1,5 @@ +-- the `addon` name is reserved for the addon references +add_addons("addon") + +target("test") + set_kind("phony") 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..43509bfeb --- /dev/null +++ b/tests/actions/addon/projects/autofetch-build/src/main.c @@ -0,0 +1,14 @@ +// 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 +#ifndef MY_C6000_TOOL +# error the tool module 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.lua b/tests/actions/addon/projects/autofetch-build/xmake.lua new file mode 100644 index 000000000..862ddf676 --- /dev/null +++ b/tests/actions/addon/projects/autofetch-build/xmake.lua @@ -0,0 +1,14 @@ +-- the addons which this project needs, they are installed automatically +add_addons("custom-include", "custom-rule", "custom-toolchain") + +-- 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/projects/autofetch-lockmiss/xmake-addons.lock b/tests/actions/addon/projects/autofetch-lockmiss/xmake-addons.lock new file mode 100644 index 000000000..750a8e609 --- /dev/null +++ b/tests/actions/addon/projects/autofetch-lockmiss/xmake-addons.lock @@ -0,0 +1,9 @@ +{ + __meta__ = { + version = "1.0" + }, + -- this version is not installed, we only install `latest` from the local directory + ["custom-include"] = { + version = "v9.9.9" + } +} diff --git a/tests/actions/addon/projects/autofetch-lockmiss/xmake.lua b/tests/actions/addon/projects/autofetch-lockmiss/xmake.lua new file mode 100644 index 000000000..a96a4e713 --- /dev/null +++ b/tests/actions/addon/projects/autofetch-lockmiss/xmake.lua @@ -0,0 +1,4 @@ +add_addons("custom-include") + +target("test") + set_kind("phony") diff --git a/tests/actions/addon/projects/autofetch/xmake.lua b/tests/actions/addon/projects/autofetch/xmake.lua new file mode 100644 index 000000000..75c909904 --- /dev/null +++ b/tests/actions/addon/projects/autofetch/xmake.lua @@ -0,0 +1,8 @@ +-- the addons which this project needs, they are installed automatically +add_addons("custom-include") + +-- the addon is installed automatically, so we can use its includes file here +includes("@addon/custom-include/check") + +target("test") + set_kind("phony") diff --git a/tests/actions/addon/projects/custom-package/src/main.c b/tests/actions/addon/projects/custom-package/src/main.c new file mode 100644 index 000000000..12208c880 --- /dev/null +++ b/tests/actions/addon/projects/custom-package/src/main.c @@ -0,0 +1,6 @@ +#include <hello_utils.h> + +int main(int argc, char** argv) { + hello_utils_greeting(); + return 0; +} diff --git a/tests/actions/addon/projects/custom-package/xmake.lua b/tests/actions/addon/projects/custom-package/xmake.lua new file mode 100644 index 000000000..1c967020d --- /dev/null +++ b/tests/actions/addon/projects/custom-package/xmake.lua @@ -0,0 +1,9 @@ +-- the package definitions come from the addon +includes("@addon/custom-package/packages") + +add_requires("hello-utils") + +target("test") + set_kind("binary") + add_files("src/main.c") + add_packages("hello-utils") diff --git a/tests/actions/addon/test.lua b/tests/actions/addon/test.lua new file mode 100644 index 000000000..fcc5c2efc --- /dev/null +++ b/tests/actions/addon/test.lua @@ -0,0 +1,507 @@ +import("core.base.global") +import("lib.detect.find_program") + +-- the addon fixtures, each of them provides one kind of payload only +-- +-- @note they are independent from each other, @see tests/actions/addon/custom-* + +function _addondir(name) + return path.join(os.scriptdir(), name) +end + +function _remove(name) + try { function () os.runv("xmake", {"addon", "--remove", "--force", name}) end } +end + +-- install the given addon fixture from its local directory +function _install(name) + _remove(name) + os.runv("xmake", {"addon", "--install", _addondir(name)}) +end + +-- run the given function with the addons installed, we always remove them again +function _with_addons(names, func) + for _, name in ipairs(names) do + _install(name) + end + try + { + func, + finally + { + -- @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 + } + } +end + +-- 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(recipes, func) + + -- @note installing from a repository goes through the package manager, and it always + -- needs git, which cannot be installed on the hosts without a package manager, + -- e.g. dragonflybsd + if not find_program("git") then + print("git not found, we skip the repository tests!") + return + end + + local suffix = path.filename(os.tmpfile()):gsub("[^%w]", "") + local reponame = "addon-test-repo-" .. suffix + local repodir = os.tmpfile() .. ".addon-repo" + 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)) + end + + -- register the repository and clear the quick search cache, it will be rebuilt on the next search + 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")) + + try + { + function () + func(reponame) + end, + finally + { + -- @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 + 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(path.join(global.cachedir(), "quick_search")) + os.tryrm(repodir) + if not ok then + raise(errors) + end + end + } + } +end + +-- 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", argv) end, + catch { function (e) errors = e end }, + finally + { + function () + os.cd(oldir) + os.tryrm(projectdir) + end + } + } + if errors then + raise(errors) + end + return out +end + +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 + { + -- @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 + } + } +end + +-- only the payloads should be installed, our own files should not +function test_install(t) + _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 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", {"hello_addon"}); return true end }) +end + +-- 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) + + -- it should fail if the addon which provides it is not installed + t:require_not(try { function () _config_project(projectfile); return true end }) +end + +-- 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 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)) + + local projectdir = os.tmpfile() .. ".addon-project" + os.tryrm(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 + +-- an addon can ship the package definitions in its includes file, so that a project +-- only needs `add_requires`, @see tests/actions/addon/custom-package +function test_include_packages(t) + + -- @note we need a compiler here, the project links against the package + if not (find_program("gcc") or find_program("clang") or find_program("cc")) then + return + end + _with_addons({"custom-package"}, function () + _with_project("custom-package", function () + t:require(os.iorunv("xmake", {"build", "-y"}):find("build ok", 1, true)) + t:require(os.iorunv("xmake", {"run"}):find("hello from the custom package", 1, true)) + end) + end) +end + +-- 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") + -- + -- @note there is no builtin `mycl6x`, so they can only come from this addon + local script2 = "import(\"core.tools.mycl6x\"); assert(mycl6x.compargv, \"invalid tool module!\"); " .. + "import(\"lib.detect.find_tool\"); assert(find_tool(\"mycl6x\"), \"mycl6x not found!\")" + os.runv("xmake", {"lua", "-c", script2}) + + -- and a project can build with it, the flags of the toolchain and of its tool module + -- are both checked by the source file + -- + -- @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("binary") + set_toolchains("@addon/custom-toolchain/my-c6000") + add_files("src/main.c") +]], {"build", "-y"}, {files = {["src/main.c"] = [[ +#ifndef MY_C6000 +# error the flags of the addon toolchain are not used! +#endif +#ifndef MY_C6000_TOOL +# error the flags of its tool module are not used! +#endif +int main(int argc, char** argv) { return 0; } +]]}}) + end + end) +end + +-- the addons which a project declares with `add_addons` 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, + -- 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") + 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 + +-- 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 + +-- a complete project which declares its addons with `add_addons` 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 + +-- a locked version which is not installed must not hide the addon +-- +-- @note the auto-fetch installs the locked version, but the commands which do not load +-- the project content only pin it, e.g. `xmake lua`, @see project._pin_addons() +-- +function test_autofetch_lock_missing_version(t) + _with_addons({"custom-include"}, function () + _with_project("autofetch-lockmiss", function () + local script = "import(\"core.package.addon\"); print(addon.addons()[\"custom-include\"] ~= nil)" + t:require(os.iorunv("xmake", {"lua", "-c", script}):find("true", 1, true)) + end) + end) +end + +-- the declared addons are checked, e.g. the reserved names +function test_autofetch_invalid(t) + _with_project("autofetch-badname", function () + t:require_not(try { function () os.runv("xmake", {"config", "-y"}); return true 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")), + ["custom-module"] = ("set_sourcedir(%q)"):format(_addondir("custom-module"))} + _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", "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 + -- + -- @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("xmake", {"lua", "private.xrepo", "search", "custom-plugin"}):find("custom-plugin", 1, true)) + + -- and several of them can be installed in one shot, they are resolved and installed together + os.runv("xmake", {"addon", "--remove", "--force", "custom-plugin"}) + os.runv("xmake", {"addon", "--install", "-y", "custom-plugin", reponame .. "@custom-module"}) + t:require(os.iorunv("xmake", {"hello_addon"}):find("hello from custom-plugin", 1, true)) + t:require(os.iorunv("xmake", {"lua", "-c", "import(\"@addon.custom-module.greeting\"); print(greeting(\"xmake\"))"}) + :find("hello from custom-module: xmake", 1, true)) + end) +end + +-- an addon can depend on the other addons, they are installed and activated together +function test_addon_deps(t) + 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", "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_addons({"custom-plugin"}, function () + + -- 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", {"hello_addon"}):find("hello from custom-plugin", 1, true)) + end) +end + +-- an addon is able to take over a builtin plugin, so we can move a deprecated builtin +-- plugin to an addon, e.g. `xmake format` +function test_install_override(t) + + -- the builtin plugin is used if we do not install the addon + t:require_not(os.iorunv("xmake", {"format", "--help"}):find("Say hello instead of formatting", 1, true)) + + _with_addons({"custom-override"}, function () + local out = os.iorunv("xmake", {"format"}) + t:require(out:find("hello from custom-override", 1, true)) + + -- and it should not be reported as a conflict + t:require_not(out:find("conflicts", 1, true)) + t:require(os.iorunv("xmake", {"format", "--help"}):find("Say hello instead of formatting", 1, true)) + end) + + -- the builtin plugin is used again after removing the addon + t:require_not(os.iorunv("xmake", {"format", "--help"}):find("Say hello instead of formatting", 1, true)) +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 + 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) + + -- the `addon` name is reserved + t:require_not(try { function () _config_project([[ +add_requires("addon") +target("test") + set_kind("phony") +]]); return true end }) +end diff --git a/tests/modules/binutils/test.lua b/tests/modules/binutils/test.lua index ce68bcda8..fe4a182f2 100644 --- a/tests/modules/binutils/test.lua +++ b/tests/modules/binutils/test.lua @@ -81,6 +81,130 @@ function test_format(t) os.tryrm(tempdir) end +function test_bin2elf(t) + local tempdir = "temp/binutils_bin2elf" + os.tryrm(tempdir) + os.mkdir(tempdir) + + -- input binary payload + local input = path.join(tempdir, "data.bin") + io.writefile(input, "hello world payload", {encoding = "binary"}) + + -- parse the ELF header fields we care about (class, data encoding, machine, flags) + local function _parse_elf_header(objectfile) + local data = io.readfile(objectfile, {encoding = "binary"}) + t:require(data ~= nil and #data >= 24) + -- magic + t:are_equal(string.byte(data, 1), 0x7f) + t:are_equal(string.byte(data, 2), string.byte("E")) + t:are_equal(string.byte(data, 3), string.byte("L")) + t:are_equal(string.byte(data, 4), string.byte("F")) + local class = string.byte(data, 5) -- e_ident[EI_CLASS]: 1 = 32-bit, 2 = 64-bit + local encode = string.byte(data, 6) -- e_ident[EI_DATA]: 1 = LSB, 2 = MSB + local bigendian = (encode == 2) + -- read a target-endian integer of nbytes at 1-based index + local function _readint(index, nbytes) + local value = 0 + for i = 0, nbytes - 1 do + local byte = string.byte(data, index + i) + if bigendian then + value = value * 256 + byte + else + value = value + byte * (256 ^ i) + end + end + return value + end + local machine = _readint(19, 2) -- e_machine at offset 18 + -- e_flags: offset 36 for 32-bit, 48 for 64-bit + local flags = (class == 2) and _readint(49, 4) or _readint(37, 4) + return {class = class, encode = encode, machine = machine, flags = flags} + end + + local function _gen(arch) + local objectfile = path.join(tempdir, arch .. ".o") + binutils.bin2obj(input, objectfile, {format = "elf", arch = arch, basename = "test"}) + return _parse_elf_header(objectfile) + end + + -- loong64: 64-bit class, LoongArch machine (issue: was wrongly detected as 32-bit) + local loong64 = _gen("loong64") + t:are_equal(loong64.class, 2) + t:are_equal(loong64.encode, 1) + t:are_equal(loong64.machine, 0x102) + t:are_equal(loong64.flags, 0x43) -- double-float ABI + object ABI v1 + + -- riscv64: double-float ABI in e_flags (issue: was soft-float, e_flags == 0) + local riscv64 = _gen("riscv64") + t:are_equal(riscv64.class, 2) + t:are_equal(riscv64.machine, 0xf3) + t:are_equal(riscv64.flags, 0x5) -- RVC + double-float + + -- riscv (32-bit): same machine/flags, 32-bit class + local riscv = _gen("riscv") + t:are_equal(riscv.class, 1) + t:are_equal(riscv.machine, 0xf3) + t:are_equal(riscv.flags, 0x5) + + -- s390x: big-endian (issue: was wrongly little-endian) + local s390x = _gen("s390x") + t:are_equal(s390x.class, 2) + t:are_equal(s390x.encode, 2) -- MSB + t:are_equal(s390x.machine, 0x16) + + -- x86_64: unchanged, little-endian 64-bit + local x86_64 = _gen("x86_64") + t:are_equal(x86_64.class, 2) + t:are_equal(x86_64.encode, 1) + t:are_equal(x86_64.machine, 0x3e) + t:are_equal(x86_64.flags, 0) + + -- mips is big-endian, mipsel is little-endian + -- 32-bit variants use the o32 ABI + CPIC (e_flags == 0x1004) + local mips = _gen("mips") + t:are_equal(mips.encode, 2) + t:are_equal(mips.machine, 0x08) + t:are_equal(mips.flags, 0x1004) + local mipsel = _gen("mipsel") + t:are_equal(mipsel.encode, 1) + t:are_equal(mipsel.machine, 0x08) + t:are_equal(mipsel.flags, 0x1004) + + -- 64-bit variants use the n64 ABI (implied by ELFCLASS64) + CPIC (e_flags == 0x4) + local mips64 = _gen("mips64") + t:are_equal(mips64.class, 2) + t:are_equal(mips64.encode, 2) + t:are_equal(mips64.machine, 0x08) + t:are_equal(mips64.flags, 0x4) + local mips64el = _gen("mips64el") + t:are_equal(mips64el.class, 2) + t:are_equal(mips64el.encode, 1) + t:are_equal(mips64el.machine, 0x08) + t:are_equal(mips64el.flags, 0x4) + + -- xmake has no separate ppc64le arch (find_platform maps powerpc64le -> ppc64) and + -- ppc64le is the dominant modern target, so a plain "ppc64" is treated as ppc64le: + -- little-endian + OpenPOWER ELFv2 ABI (e_flags low bits == 2) + local ppc64 = _gen("ppc64") + t:are_equal(ppc64.encode, 1) + t:are_equal(ppc64.machine, 0x15) + t:are_equal(ppc64.flags, 0x2) + + -- explicit ppc64le behaves the same + local ppc64le = _gen("ppc64le") + t:are_equal(ppc64le.encode, 1) + t:are_equal(ppc64le.machine, 0x15) + t:are_equal(ppc64le.flags, 0x2) + + -- explicit big-endian ppc64be keeps the ELFv1 ABI + local ppc64be = _gen("ppc64be") + t:are_equal(ppc64be.encode, 2) + t:are_equal(ppc64be.machine, 0x15) + t:are_equal(ppc64be.flags, 0x1) + + os.tryrm(tempdir) +end + function test_deplibs(t) local tempdir = "temp/binutils_deplibs" os.tryrm(tempdir) diff --git a/tests/modules/project_depend/test.lua b/tests/modules/project_depend/test.lua new file mode 100644 index 000000000..2df79ba16 --- /dev/null +++ b/tests/modules/project_depend/test.lua @@ -0,0 +1,19 @@ +import("core.project.depend") + +function test_nested_values_append(t) + local dependinfo = {values = {{"clang", "-m64"}}} + local changed = depend.is_changed(dependinfo, {values = {{"clang", "-m64", "-DFEATURE_ON"}}}) + t:require(changed) +end + +function test_nested_values_remove(t) + local dependinfo = {values = {{"clang", "-m64", "-DFEATURE_ON"}}} + local changed = depend.is_changed(dependinfo, {values = {{"clang", "-m64"}}}) + t:require(changed) +end + +function test_nested_values_unchanged(t) + local dependinfo = {values = {{"clang", "-m64", "-DFEATURE_ON"}}} + local changed = depend.is_changed(dependinfo, {values = {{"clang", "-m64", "-DFEATURE_ON"}}}) + t:require_not(changed) +end diff --git a/tests/modules/semver/test.lua b/tests/modules/semver/test.lua index 1ab6dc8aa..57d213f4a 100755 --- a/tests/modules/semver/test.lua +++ b/tests/modules/semver/test.lua @@ -7,6 +7,12 @@ function _check_semver_select(t, results, required_ver, versions, tags, branches t:are_equal(source, results[2]) end +function _check_semver_select_failed(t, required_ver, versions, tags, branches) + t:will_raise(function() + semver.select(required_ver, versions or {}, tags or {}, branches or {}) + end, "unable to select version") +end + -- test select version function test_semver_select(t) @@ -18,15 +24,101 @@ function test_semver_select(t) , "^1.5.0" ,{"1.4.0", "1.5.0", "1.5.1"}) + _check_semver_select(t, {"3.53.0+200", "version"} + , "3.53.0+200" + , {"3.53.0+0", "3.53.0+100", "3.53.0+200"}) + + _check_semver_select_failed(t, "3.53.0+999", {"3.53.0+100"}) + + _check_semver_select(t, {"3.53.0+200", "version"} + , "3.53.0" + , {"3.53.0+0", "3.53.0+100", "3.53.0+200"}) + + _check_semver_select(t, {"3.53.0+200", "version"} + , "3.53.0" + , {"3.53.0+200", "3.53.0+100", "3.53.0+0"}) + + _check_semver_select(t, {"3.53.0+beta", "version"} + , "3.53.0" + , {"3.53.0+alpha", "3.53.0+beta"}) + + _check_semver_select(t, {"3.53.0+beta", "version"} + , "3.53.0" + , {"3.53.0+beta", "3.53.0+alpha"}) + + _check_semver_select(t, {"1.2.3", "version"} + , "1.2.3" + , {"1.2.3+1", "1.2.3"}) + + _check_semver_select(t, {"1.2.3", "version"} + , "=1.2.3" + , {"1.2.3+7", "1.2.3"}) + + _check_semver_select(t, {"1.2.9", "version"} + , "1.2" + , {"1.2", "1.2.9"}) + + _check_semver_select(t, {"1.9.0", "version"} + , "^1.2.3" + , {"^1.2.3", "1.2.3", "1.9.0"}) + + _check_semver_select(t, {"1.2.3", "tag"} + , "1.2.3" + , {"v1.2.3+7"} + , {"1.2.3"}) + + _check_semver_select(t, {"v1.2.3+7", "version"} + , "1.2.3+7" + , {"v1.2.3+8", "v1.2.3+7"}) + + _check_semver_select(t, {"1.2.3+7", "version"} + , "v1.2.3+7" + , {"1.2.3+8", "1.2.3+7"}) + + _check_semver_select(t, {"3.53.0+200", "tag"} + , "3.53.0+200" + , nil + , {"3.53.0+0", "3.53.0+200"}) + + _check_semver_select_failed(t, "3.53.0+999", nil, {"3.53.0+100"}) + + _check_semver_select(t, {"v1.2.3+7", "tag"} + , "=1.2.3+7" + , nil + , {"v1.2.3+7", "v1.2.3+8"}) + + _check_semver_select(t, {"v1.2.3+7", "tag"} + , "1.2.3+7" + , {"1.2.3+8"} + , {"v1.2.3+7"}) + _check_semver_select(t, {"master", "branch"} , "master" , {"1.4.0", "1.5.0", "1.5.1"} , {"v1.2.0", "v1.6.0"} , {"master", "dev"}) + _check_semver_select(t, {"next", "branch"} + , "next" + , nil + , {"vnext"} + , {"next"}) + _check_semver_select(t, {"1.5.1", "version"} , "latest" , {"1.4.0", "1.5.0", "1.5.1"}) + + _check_semver_select(t, {"1.0.0+10", "version"} + , "latest" + , {"1.0.0+9", "1.0.0+10"}) + + _check_semver_select(t, {"1.0.0+rev.10", "version"} + , "latest" + , {"1.0.0+rev.9", "1.0.0+rev.10"}) + + _check_semver_select(t, {"3.53.0+200", "version"} + , "latest" + , {"3.53.0+0", "3.53.0+200", "3.53.0+100"}) end -- select version diff --git a/tests/projects/c++/modules/precompile_reduced_bmi/src/base.mpp b/tests/projects/c++/modules/precompile_reduced_bmi/src/base.mpp new file mode 100644 index 000000000..e3e6224f2 --- /dev/null +++ b/tests/projects/c++/modules/precompile_reduced_bmi/src/base.mpp @@ -0,0 +1,5 @@ +export module base; + +export int base_value() { + return 40; +} diff --git a/tests/projects/c++/modules/precompile_reduced_bmi/src/hello.mpp b/tests/projects/c++/modules/precompile_reduced_bmi/src/hello.mpp new file mode 100644 index 000000000..9de491171 --- /dev/null +++ b/tests/projects/c++/modules/precompile_reduced_bmi/src/hello.mpp @@ -0,0 +1,7 @@ +export module hello; + +import base; + +export int answer() { + return base_value() + 2; +} diff --git a/tests/projects/c++/modules/precompile_reduced_bmi/src/main.cpp b/tests/projects/c++/modules/precompile_reduced_bmi/src/main.cpp new file mode 100644 index 000000000..75adbd883 --- /dev/null +++ b/tests/projects/c++/modules/precompile_reduced_bmi/src/main.cpp @@ -0,0 +1,5 @@ +import hello; + +int main() { + return answer() == 42 ? 0 : 1; +} diff --git a/tests/projects/c++/modules/precompile_reduced_bmi/test.lua b/tests/projects/c++/modules/precompile_reduced_bmi/test.lua new file mode 100644 index 000000000..765290f7b --- /dev/null +++ b/tests/projects/c++/modules/precompile_reduced_bmi/test.lua @@ -0,0 +1,107 @@ +inherit(".test_base") +import("core.base.json") + +local _CLANG_MIN_VER = "23" + +function clang_min_ver() + return _CLANG_MIN_VER +end + +function _check_commands(config, outdata) + local has_precompile_reduced_bmi = outdata:find("--precompile-reduced-bmi", 1, true) ~= nil + local expect_precompile_reduced_bmi = config.precompile_reduced_bmi and config.two_phases + assert(has_precompile_reduced_bmi == expect_precompile_reduced_bmi, + "unexpected --precompile-reduced-bmi usage\n%s", outdata) + + if config.two_phases then + local object_command + for _, line in ipairs(outdata:split("\n", {plain = true})) do + if line:find("hello.mpp", 1, true) and + (line:find("hello.mpp.o", 1, true) or line:find("hello.mpp.obj", 1, true)) and + line:find(" -c ", 1, true) and + not line:find("clang-scan-deps", 1, true) and + not line:find("--precompile", 1, true) then + object_command = line + break + end + end + assert(object_command, "module object command missing\n%s", outdata) + local consumes_bmi = object_command:find("hello.pcm", 1, true) ~= nil + assert(consumes_bmi ~= expect_precompile_reduced_bmi, "unexpected module object input\n%s", outdata) + end +end + +function _check_incremental() + local outdata = os.iorun("xmake -v") + if outdata:find("compiling", 1, true) or + outdata:find("linking", 1, true) or + outdata:find("generating", 1, true) then + raise("Modules incremental compilation does not work\n%s", outdata) + end +end + +function _check_compile_commands(config) + os.run("xmake project -k compile_commands") + local bmi_command + local object_command + for _, command in ipairs(assert(json.loadfile("compile_commands.json"))) do + if command.file:endswith("hello.mpp") then + local commandline = command.command or table.concat(command.arguments or {}, " ") + if commandline:find("--precompile", 1, true) then + bmi_command = commandline + elseif commandline:find("hello.mpp.o", 1, true) or commandline:find("hello.mpp.obj", 1, true) then + object_command = commandline + end + end + end + assert(bmi_command, "module BMI command missing from compile_commands.json") + assert(object_command, "module object command missing from compile_commands.json") + local expect_precompile_reduced_bmi = config.precompile_reduced_bmi and config.two_phases + assert((bmi_command:find("--precompile-reduced-bmi", 1, true) ~= nil) == expect_precompile_reduced_bmi, + "unexpected module BMI command\n%s", bmi_command) + assert(object_command:find("hello.mpp", 1, true) and not object_command:find("hello.pcm", 1, true), + "unexpected module object command\n%s", object_command) +end + +function _configure(config) + local argv = {"f", "--yes", "--toolchain=" .. config.toolchain} + if config.platform then + table.join2(argv, {"-p", config.platform}) + end + if config.runtimes then + table.insert(argv, "--runtimes=" .. config.runtimes) + end + local policies = "--policies=build.c++.modules.std:" .. (config.stdmodule and "y" or "n") + policies = policies .. ",build.c++.modules.fallbackscanner:" .. (config.fallbackscanner and "y" or "n") + policies = policies .. ",build.c++.modules.two_phases:" .. (config.two_phases and "y" or "n") + policies = policies .. ",build.c++.modules.clang.precompile_reduced_bmi:" .. (config.precompile_reduced_bmi and "y" or "n") + table.insert(argv, policies) + table.join2(argv, config.flags or {}) + os.execv("xmake", argv) +end + +function _check_build(config) + local outdata = os.iorun("xmake -rv") + _check_commands(config, outdata) + + os.run("xmake run") + _check_incremental() + if config.two_phases then + _check_compile_commands(config) + end + + if config.two_phases then + local toggled_config = table.clone(config) + toggled_config.precompile_reduced_bmi = not config.precompile_reduced_bmi + _configure(toggled_config) + local toggled_outdata = os.iorun("xmake -v") + _check_commands(toggled_config, toggled_outdata) + os.run("xmake run") + _check_incremental() + _check_compile_commands(toggled_config) + end +end + +function main(_) + run_tests({compiler = "clang", version = clang_min_ver(), precompile_reduced_bmi = true, build = _check_build}) +end diff --git a/tests/projects/c++/modules/precompile_reduced_bmi/xmake.lua b/tests/projects/c++/modules/precompile_reduced_bmi/xmake.lua new file mode 100644 index 000000000..0aa2dc516 --- /dev/null +++ b/tests/projects/c++/modules/precompile_reduced_bmi/xmake.lua @@ -0,0 +1,6 @@ +add_rules("mode.release", "mode.debug") +set_languages("c++20") + +target("precompile_reduced_bmi") + set_kind("binary") + add_files("src/*.cpp", "src/*.mpp") diff --git a/tests/projects/c++/modules/stdmodules_deps/src/bar.mpp b/tests/projects/c++/modules/stdmodules_deps/src/bar.mpp index 001cd7c2d..ab104076c 100644 --- a/tests/projects/c++/modules/stdmodules_deps/src/bar.mpp +++ b/tests/projects/c++/modules/stdmodules_deps/src/bar.mpp @@ -4,11 +4,9 @@ import std; export auto my_sum2(std::size_t a, std::size_t b) -> std::size_t; -#if defined(__GNUC__) && !defined(__clang__) -inline -#else -module :private; -#endif - auto my_sum2(std::size_t a, std::size_t b) -> std::size_t { +// Clang 23 implements P1857R3, which rejects module directives controlled by +// preprocessing conditionals. This test therefore uses an inline definition. +// See https://wg21.link/P1857R3 +inline auto my_sum2(std::size_t a, std::size_t b) -> std::size_t { return a + a + b + b; } diff --git a/tests/projects/c++/modules/stdmodules_deps_ordering/src/foo.cpp b/tests/projects/c++/modules/stdmodules_deps_ordering/src/foo.cpp new file mode 100644 index 000000000..5b9d1194e --- /dev/null +++ b/tests/projects/c++/modules/stdmodules_deps_ordering/src/foo.cpp @@ -0,0 +1,5 @@ +module foo; + +auto my_sum(int a, int b) -> int { + return a + b; +} diff --git a/tests/projects/c++/modules/stdmodules_deps_ordering/src/foo.mpp b/tests/projects/c++/modules/stdmodules_deps_ordering/src/foo.mpp new file mode 100644 index 000000000..4c9a649ca --- /dev/null +++ b/tests/projects/c++/modules/stdmodules_deps_ordering/src/foo.mpp @@ -0,0 +1,3 @@ +export module foo; + +export auto my_sum(int a, int b) -> int; diff --git a/tests/projects/c++/modules/stdmodules_deps_ordering/src/main.cpp b/tests/projects/c++/modules/stdmodules_deps_ordering/src/main.cpp new file mode 100644 index 000000000..1f87ceb59 --- /dev/null +++ b/tests/projects/c++/modules/stdmodules_deps_ordering/src/main.cpp @@ -0,0 +1,8 @@ +import foo; + +import std; + +int main() { + std::cout << my_sum(1, 2) << std::endl; + return 0; +} diff --git a/tests/projects/c++/modules/stdmodules_deps_ordering/test.lua b/tests/projects/c++/modules/stdmodules_deps_ordering/test.lua new file mode 100644 index 000000000..e77adc4ae --- /dev/null +++ b/tests/projects/c++/modules/stdmodules_deps_ordering/test.lua @@ -0,0 +1 @@ +inherit(".test_stdmodules") diff --git a/tests/projects/c++/modules/stdmodules_deps_ordering/xmake.lua b/tests/projects/c++/modules/stdmodules_deps_ordering/xmake.lua new file mode 100644 index 000000000..09c3f6a94 --- /dev/null +++ b/tests/projects/c++/modules/stdmodules_deps_ordering/xmake.lua @@ -0,0 +1,19 @@ +add_rules("mode.debug", "mode.release") +set_languages("c++latest") + +-- test that a plain c++ file is ordered after all the bmis it reuses from its deps, +-- not only after the last one +-- +-- @note foo must not import std, otherwise its bmi and the std bmi +-- would not be built by two distinct jobs of the foo target +-- +-- @see https://github.com/xmake-io/xmake/issues/7690 +target("foo") + set_kind("static") + add_files("src/foo.cpp") + add_files("src/foo.mpp", {public = true}) + +target("main") + set_kind("binary") + add_deps("foo") + add_files("src/main.cpp") diff --git a/tests/projects/c++/modules/test_base.lua b/tests/projects/c++/modules/test_base.lua index 372229695..2d0fa3f57 100644 --- a/tests/projects/c++/modules/test_base.lua +++ b/tests/projects/c++/modules/test_base.lua @@ -103,6 +103,9 @@ function build_tests(toolchain_name, opt) local policies = "--policies=build.c++.modules.std:" .. (opt.stdmodule and "y" or "n") policies = policies .. ",build.c++.modules.fallbackscanner:" .. (opt.fallbackscanner and "y" or "n") policies = policies .. ",build.c++.modules.two_phases:" .. (two_phases and "y" or "n") + if opt.precompile_reduced_bmi ~= nil then + policies = policies .. ",build.c++.modules.clang.precompile_reduced_bmi:" .. (opt.precompile_reduced_bmi and "y" or "n") + end local platform = " " if opt.platform then @@ -127,7 +130,7 @@ function build_tests(toolchain_name, opt) os.exec("xmake clean -a") os.exec("xmake f" .. platform .. "--toolchain=" .. toolchain_name .. runtimes .. "-c --yes " .. policies .. flags) if opt.build then - opt.build() + opt.build(table.join(opt, {toolchain = toolchain_name, two_phases = two_phases})) else _build(opt.check_outdata) end @@ -150,9 +153,12 @@ function run_tests(clang_options, gcc_options, msvc_options) if not clang_options.disable_clang_cl then local clang_cl_options = table.clone(clang_options) clang_cl_options.compiler = "clang-cl" - clang_cl_options.version = clang_cl_min_ver() + local clang_cl_minver = clang_cl_min_ver() + if semver.compare(clang_cl_options.version, clang_cl_minver) < 0 then + clang_cl_options.version = clang_cl_minver + end build_tests("clang-cl", clang_cl_options) - build_tests("clang-cl", table.join(clang_options, {two_phases = false})) + build_tests("clang-cl", table.join(clang_cl_options, {two_phases = false})) end if clang_options.stdmodule then wprint("std modules tests skipped for Windows clang libc++ as it's not currently supported officially") diff --git a/tests/projects/embed/esp32/blink/boards/esp32c3/board.h b/tests/projects/embed/esp32/blink/boards/esp32c3/board.h new file mode 100644 index 000000000..126f2c2c3 --- /dev/null +++ b/tests/projects/embed/esp32/blink/boards/esp32c3/board.h @@ -0,0 +1,7 @@ +// the board configuration of esp32c3, it's selected by the `board` option +// +// @note gpio12-17 are the spi flash of the esp32c3, do not use them +// +#pragma once + +#define LED_GPIO 8 diff --git a/tests/projects/embed/esp32/blink/src/main.c b/tests/projects/embed/esp32/blink/src/main.c new file mode 100644 index 000000000..b51e3b67b --- /dev/null +++ b/tests/projects/embed/esp32/blink/src/main.c @@ -0,0 +1,26 @@ +// blink the led of the board and print a heartbeat +// +// the led pin comes from `boards/<board>/board.h`, it's selected by the board option, +// e.g. xmake f --board=esp32c3 +// +// @note the entry is the esp-idf native `void app_main(void)`, and we print with +// `esp_rom_printf`, it writes to the rom console, which is the usb port on the +// boards booting over the native usb (usb-serial-jtag), e.g. xmake monitor +// +#include "board.h" +#include "driver/gpio.h" +#include "esp_rom_sys.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +void app_main(void) +{ + esp_rom_printf("blink: toggle gpio %d every 500ms\n", LED_GPIO); + gpio_reset_pin(LED_GPIO); + gpio_set_direction(LED_GPIO, GPIO_MODE_OUTPUT); + for (int count = 0; ; count++) { + gpio_set_level(LED_GPIO, count & 1); + esp_rom_printf("blink: tick %d\n", count); + vTaskDelay(pdMS_TO_TICKS(500)); + } +} diff --git a/tests/projects/embed/esp32/blink/xmake-addons.lua b/tests/projects/embed/esp32/blink/xmake-addons.lua new file mode 100644 index 000000000..aea4ad480 --- /dev/null +++ b/tests/projects/embed/esp32/blink/xmake-addons.lua @@ -0,0 +1,2 @@ +-- the addon is installed automatically when we load this project +add_addons("esp32-devel") diff --git a/tests/projects/embed/esp32/blink/xmake.lua b/tests/projects/embed/esp32/blink/xmake.lua new file mode 100644 index 000000000..3fea2dd12 --- /dev/null +++ b/tests/projects/embed/esp32/blink/xmake.lua @@ -0,0 +1,15 @@ +-- the esp32 blink example, it needs the esp32-devel addon +-- +-- $ xmake f --board=esp32c3 +-- $ xmake +-- $ xmake install -- flash the image to the board +-- $ xmake run -- flash it and monitor the serial output +-- +includes("@addon/esp32-devel/board") + +target("blink") + add_rules("@addon/esp32-devel/app") + add_files("src/*.c") + + -- the led pin of the board, @see boards/<board>/board.h + add_includedirs("boards/" .. (get_config("board") or "esp32c3")) diff --git a/tests/projects/other/bin2c/src/asset.bin b/tests/projects/other/bin2c/src/asset.bin new file mode 100644 index 000000000..db1331f22 --- /dev/null +++ b/tests/projects/other/bin2c/src/asset.bin @@ -0,0 +1 @@ +hello transform!
\ No newline at end of file diff --git a/tests/projects/other/bin2c/src/asset2.bin b/tests/projects/other/bin2c/src/asset2.bin new file mode 100644 index 000000000..d7ffc4b96 --- /dev/null +++ b/tests/projects/other/bin2c/src/asset2.bin @@ -0,0 +1 @@ +hello luafile!
\ No newline at end of file diff --git a/tests/projects/other/bin2c/src/main.c b/tests/projects/other/bin2c/src/main.c index 74898132c..d85e9934e 100644 --- a/tests/projects/other/bin2c/src/main.c +++ b/tests/projects/other/bin2c/src/main.c @@ -1,6 +1,7 @@ #include <stdio.h> #include <stdint.h> #include <ctype.h> +#include <string.h> static unsigned char g_bin_data[] = { #include "data.bin.h" @@ -10,6 +11,14 @@ static unsigned char g_ico_data[] = { #include "xmake.ico.h" }; +static unsigned char g_asset_data[] = { + #include "asset.bin.h" +}; + +static unsigned char g_asset2_data[] = { + #include "asset2.bin.h" +}; + static void hexdump(const char* name, const uint8_t* data, uint32_t size) { printf("%s: size: %u bytes\n", name, (unsigned int)size); if (size == 0) { @@ -94,5 +103,25 @@ int main(int argc, char** argv) hexdump("data.bin", g_bin_data, (uint32_t)sizeof(g_bin_data)); printf("\n"); hexdump("xmake.ico", g_ico_data, (uint32_t)sizeof(g_ico_data)); + printf("\n"); + hexdump("asset.bin (transformed)", g_asset_data, (uint32_t)sizeof(g_asset_data)); + printf("\n"); + hexdump("asset2.bin (transformed by lua file)", g_asset2_data, (uint32_t)sizeof(g_asset2_data)); + + // verify the function transform: asset.bin must be the reverse of "hello transform!" (+ zeroend '\0') + const char* expected = "!mrofsnart olleh"; + if (sizeof(g_asset_data) != 17 || memcmp(g_asset_data, expected, 16) != 0) { + printf("asset.bin: transform verification failed!\n"); + return 1; + } + printf("asset.bin: transform verification ok\n"); + + // verify the lua-file transform: asset2.bin must be the reverse of "hello luafile!" (+ zeroend '\0') + const char* expected2 = "!elifaul olleh"; + if (sizeof(g_asset2_data) != 15 || memcmp(g_asset2_data, expected2, 14) != 0) { + printf("asset2.bin: transform verification failed!\n"); + return 1; + } + printf("asset2.bin: transform verification ok\n"); return 0; } diff --git a/tests/projects/other/bin2c/test.lua b/tests/projects/other/bin2c/test.lua index b57362078..a23bb98a3 100644 --- a/tests/projects/other/bin2c/test.lua +++ b/tests/projects/other/bin2c/test.lua @@ -1,3 +1,5 @@ function main(t) t:build() + -- run the target, main.c verifies the transformed asset.bin and exits non-zero on mismatch + os.exec("xmake run test") end diff --git a/tests/projects/other/bin2c/transform.lua b/tests/projects/other/bin2c/transform.lua new file mode 100644 index 000000000..54cf1f394 --- /dev/null +++ b/tests/projects/other/bin2c/transform.lua @@ -0,0 +1,5 @@ +function main(inputfile, outputfile) + import("core.base.bytes") + local data = io.readfile(inputfile, {encoding = "binary"}) + io.writefile(outputfile, data:reverse(), {encoding = "binary"}) +end diff --git a/tests/projects/other/bin2c/xmake.lua b/tests/projects/other/bin2c/xmake.lua index b00dedb65..d60aea710 100644 --- a/tests/projects/other/bin2c/xmake.lua +++ b/tests/projects/other/bin2c/xmake.lua @@ -4,7 +4,15 @@ target("test") set_kind("binary") add_rules("utils.bin2c", {linewidth = 16, extensions = {".bin", ".ico"}}) add_files("src/*.c") - add_files("src/*.bin") + add_files("src/data.bin") add_files("src/xmake.ico", {nozeroend = true}) + add_files("src/asset.bin", {transform = function (inputfile, outputfile, opt) + import("core.base.bytes") + assert(bytes and opt.target) + local data = io.readfile(inputfile, {encoding = "binary"}) + io.writefile(outputfile, data:reverse(), {encoding = "binary"}) + end}) + -- transform can also be a lua script file (supported by the project generators) + add_files("src/asset2.bin", {transform = path.join(os.projectdir(), "transform.lua")}) diff --git a/tests/projects/other/bin2obj/src/asset.bin b/tests/projects/other/bin2obj/src/asset.bin new file mode 100644 index 000000000..db1331f22 --- /dev/null +++ b/tests/projects/other/bin2obj/src/asset.bin @@ -0,0 +1 @@ +hello transform!
\ No newline at end of file diff --git a/tests/projects/other/bin2obj/src/asset2.bin b/tests/projects/other/bin2obj/src/asset2.bin new file mode 100644 index 000000000..d7ffc4b96 --- /dev/null +++ b/tests/projects/other/bin2obj/src/asset2.bin @@ -0,0 +1 @@ +hello luafile!
\ No newline at end of file diff --git a/tests/projects/other/bin2obj/src/main.c b/tests/projects/other/bin2obj/src/main.c index bc1d2442f..03c50352d 100644 --- a/tests/projects/other/bin2obj/src/main.c +++ b/tests/projects/other/bin2obj/src/main.c @@ -1,6 +1,7 @@ #include <stdio.h> #include <stdint.h> #include <ctype.h> +#include <string.h> extern const uint8_t _binary_data_bin_start[]; extern const uint8_t _binary_data_bin_end[]; @@ -8,30 +9,39 @@ extern const uint8_t _binary_data_bin_end[]; extern const uint8_t _binary_xmake_ico_start[]; extern const uint8_t _binary_xmake_ico_end[]; +extern const uint8_t _binary_asset_bin_start[]; +extern const uint8_t _binary_asset_bin_end[]; + +extern const uint8_t _binary_asset2_bin_start[]; +extern const uint8_t _binary_asset2_bin_end[]; + +extern const uint8_t _binary_asset2_bin_start[]; +extern const uint8_t _binary_asset2_bin_end[]; + static void hexdump(const char* name, const uint8_t* data, uint32_t size) { printf("%s: size: %u bytes\n", name, (unsigned int)size); if (size == 0) { return; } - + // if file is larger than 128 bytes, only dump first 64 and last 64 bytes uint32_t dump_size = size; uint32_t start_offset = 0; uint32_t end_offset = 0; uint32_t skip_size = 0; - + if (size > 128) { dump_size = 64; start_offset = 0; end_offset = size - 64; skip_size = end_offset - start_offset - dump_size; } - + // dump start for (uint32_t offset = start_offset; offset < start_offset + dump_size; offset += 16) { // print offset printf("%08x ", (unsigned int)offset); - + // print hex bytes (8 bytes, space, 8 bytes) for (uint32_t i = 0; i < 16; i++) { if (offset + i < size) { @@ -43,7 +53,7 @@ static void hexdump(const char* name, const uint8_t* data, uint32_t size) { printf(" "); } } - + // print ASCII representation printf(" |"); for (uint32_t i = 0; i < 16 && offset + i < size; i++) { @@ -52,18 +62,18 @@ static void hexdump(const char* name, const uint8_t* data, uint32_t size) { } printf("|\n"); } - + // print skip indicator if needed if (skip_size > 0) { printf(" ... (skipped %u bytes) ...\n", (unsigned int)skip_size); } - + // dump end if needed if (size > 128) { for (uint32_t offset = end_offset; offset < size; offset += 16) { // print offset printf("%08x ", (unsigned int)offset); - + // print hex bytes (8 bytes, space, 8 bytes) for (uint32_t i = 0; i < 16; i++) { if (offset + i < size) { @@ -75,7 +85,7 @@ static void hexdump(const char* name, const uint8_t* data, uint32_t size) { printf(" "); } } - + // print ASCII representation printf(" |"); for (uint32_t i = 0; i < 16 && offset + i < size; i++) { @@ -90,9 +100,31 @@ static void hexdump(const char* name, const uint8_t* data, uint32_t size) { int main(int argc, char** argv) { const uint32_t _binary_data_bin_size = (uint32_t)(_binary_data_bin_end - _binary_data_bin_start); const uint32_t _binary_xmake_ico_size = (uint32_t)(_binary_xmake_ico_end - _binary_xmake_ico_start); + const uint32_t _binary_asset_bin_size = (uint32_t)(_binary_asset_bin_end - _binary_asset_bin_start); + const uint32_t _binary_asset2_bin_size = (uint32_t)(_binary_asset2_bin_end - _binary_asset2_bin_start); hexdump("data.bin", _binary_data_bin_start, _binary_data_bin_size); printf("\n"); hexdump("xmake.ico", _binary_xmake_ico_start, _binary_xmake_ico_size); + printf("\n"); + hexdump("asset.bin (transformed)", _binary_asset_bin_start, _binary_asset_bin_size); + printf("\n"); + hexdump("asset2.bin (transformed by lua file)", _binary_asset2_bin_start, _binary_asset2_bin_size); + + // verify the function transform: asset.bin must be the reverse of "hello transform!" (+ zeroend '\0') + const char* expected = "!mrofsnart olleh"; + if (_binary_asset_bin_size != 17 || memcmp(_binary_asset_bin_start, expected, 16) != 0) { + printf("asset.bin: transform verification failed!\n"); + return 1; + } + printf("asset.bin: transform verification ok\n"); + + // verify the lua-file transform: asset2.bin must be the reverse of "hello luafile!" (+ zeroend '\0') + const char* expected2 = "!elifaul olleh"; + if (_binary_asset2_bin_size != 15 || memcmp(_binary_asset2_bin_start, expected2, 14) != 0) { + printf("asset2.bin: transform verification failed!\n"); + return 1; + } + printf("asset2.bin: transform verification ok\n"); return 0; } diff --git a/tests/projects/other/bin2obj/test.lua b/tests/projects/other/bin2obj/test.lua index 7f2050b62..a75aa7e40 100644 --- a/tests/projects/other/bin2obj/test.lua +++ b/tests/projects/other/bin2obj/test.lua @@ -1,4 +1,6 @@ function main(t) t:build() + -- run the target, main.c verifies the transformed asset.bin and exits non-zero on mismatch + os.exec("xmake run test") end diff --git a/tests/projects/other/bin2obj/transform.lua b/tests/projects/other/bin2obj/transform.lua new file mode 100644 index 000000000..54cf1f394 --- /dev/null +++ b/tests/projects/other/bin2obj/transform.lua @@ -0,0 +1,5 @@ +function main(inputfile, outputfile) + import("core.base.bytes") + local data = io.readfile(inputfile, {encoding = "binary"}) + io.writefile(outputfile, data:reverse(), {encoding = "binary"}) +end diff --git a/tests/projects/other/bin2obj/xmake.lua b/tests/projects/other/bin2obj/xmake.lua index 9541e17ae..d383bb1bb 100644 --- a/tests/projects/other/bin2obj/xmake.lua +++ b/tests/projects/other/bin2obj/xmake.lua @@ -6,3 +6,11 @@ target("test") add_files("src/*.c") add_files("src/data.bin", {zeroend = true}) add_files("src/xmake.ico", {zeroend = false}) + add_files("src/asset.bin", {zeroend = true, transform = function (inputfile, outputfile, opt) + import("core.base.bytes") + assert(bytes and opt.target) + local data = io.readfile(inputfile, {encoding = "binary"}) + io.writefile(outputfile, data:reverse(), {encoding = "binary"}) + end}) + -- transform can also be a lua script file (supported by the project generators) + add_files("src/asset2.bin", {zeroend = true, transform = path.join(os.projectdir(), "transform.lua")}) |
