From 791bb25721f76b49b384b6f8657bac7660036b4a Mon Sep 17 00:00:00 2001 From: Arthur LAURENT Date: Tue, 4 Nov 2025 12:15:52 +0100 Subject: add tests --- tests/projects/c++/modules/test_xmake_test.lua | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 tests/projects/c++/modules/test_xmake_test.lua (limited to 'tests/projects/c++/modules/test_xmake_test.lua') diff --git a/tests/projects/c++/modules/test_xmake_test.lua b/tests/projects/c++/modules/test_xmake_test.lua new file mode 100644 index 000000000..6ad2414f6 --- /dev/null +++ b/tests/projects/c++/modules/test_xmake_test.lua @@ -0,0 +1,23 @@ +inherit("test_base") +import("utils.ci.is_running", {alias = "ci_is_running"}) + +CLANG_MIN_VER = is_subhost("windows") and "19" or "17" +GCC_MIN_VER = "11" +MSVC_MIN_VER = "14.29" + +function run_xmake_test(...) + local flags = "" + if ci_is_running() then + flags = "-vD" + end + local outdata, errdata = os.iorun("xmake test " .. flags) + print(outdata, errdata) + -- assert(outdata, errdata) +end + +function main(t) + local clang_options = {compiler = "clang", version = CLANG_MIN_VER, after_build = run_xmake_test} + local gcc_options = {compiler = "gcc", version = GCC_MIN_VER, after_build = run_xmake_test} + local msvc_options = {version = MSVC_MIN_VER, after_build = run_xmake_test} + run_tests(clang_options, gcc_options, msvc_options) +end -- cgit v1.3.1 From ce723897432dad8d8f94ddf831e3a8d7db6e2700 Mon Sep 17 00:00:00 2001 From: Arthur LAURENT Date: Tue, 4 Nov 2025 12:36:33 +0100 Subject: fix module reusage with xmake test --- tests/projects/c++/modules/test_xmake_test.lua | 3 +- xmake/rules/c++/modules/builder.lua | 11 +- xmake/rules/c++/modules/scanner.lua | 13 +- xmake/rules/c++/modules/support.lua | 226 +++++++++++++------------ 4 files changed, 133 insertions(+), 120 deletions(-) (limited to 'tests/projects/c++/modules/test_xmake_test.lua') diff --git a/tests/projects/c++/modules/test_xmake_test.lua b/tests/projects/c++/modules/test_xmake_test.lua index 6ad2414f6..2d0b0a2d1 100644 --- a/tests/projects/c++/modules/test_xmake_test.lua +++ b/tests/projects/c++/modules/test_xmake_test.lua @@ -11,8 +11,7 @@ function run_xmake_test(...) flags = "-vD" end local outdata, errdata = os.iorun("xmake test " .. flags) - print(outdata, errdata) - -- assert(outdata, errdata) + assert(outdata, errdata) end function main(t) diff --git a/xmake/rules/c++/modules/builder.lua b/xmake/rules/c++/modules/builder.lua index a1c93a35f..87cd143f0 100644 --- a/xmake/rules/c++/modules/builder.lua +++ b/xmake/rules/c++/modules/builder.lua @@ -121,13 +121,15 @@ function _get_jobdeps(target, module, jobgraph, buildfilejob) return jobdeps end -function _get_saved_jobdeps_for(buildfilejob) +function _get_saved_jobdeps_for(jobgraph, buildfilejob) local memcache = support.memcache() local dependent_jobs = memcache:get2("dependent_jobs", buildfilejob) local jobdeps = {} for _, dependent_job in ipairs(dependent_jobs) do - jobdeps[dependent_job] = jobdeps[dependent_job] or {} - table.insert(jobdeps[dependent_job], buildfilejob) + if jobgraph:has(dependent_job) then + jobdeps[dependent_job] = jobdeps[dependent_job] or {} + table.insert(jobdeps[dependent_job], buildfilejob) + end end return jobdeps end @@ -266,7 +268,7 @@ function build_modules_for_jobgraph(target, jobgraph, built_modules) -- insert saved jobdeps for _, buildfilejob in ipairs(buildfilejobs) do - table.join2(jobdeps, _get_saved_jobdeps_for(buildfilejob)) + table.join2(jobdeps, _get_saved_jobdeps_for(jobgraph, buildfilejob)) end -- apply jobdeps @@ -756,4 +758,3 @@ function build_objectfiles(target, jobgraph, _, opt) profiler.leave(target:fullname(), "c++ modules", "builder", "objectfiles") end end - diff --git a/xmake/rules/c++/modules/scanner.lua b/xmake/rules/c++/modules/scanner.lua index 818e37aa2..3c7a04aa4 100644 --- a/xmake/rules/c++/modules/scanner.lua +++ b/xmake/rules/c++/modules/scanner.lua @@ -234,7 +234,6 @@ function _get_packages_for(target) for name, dep in pairs(target:orderdeps()) do local dep_packages = _get_packages_for(dep) for pkgname, package in pairs(dep_packages) do - -- print(package) packages[pkgname] = {pkg = package.pkg, from_dep = package.from_dep or dep, from_package = true} end end @@ -377,13 +376,13 @@ function _patch_sourcebatch(target, sourcebatch) local localcache = support.localcache() local reuse = target:policy("build.c++.modules.reuse") or - target:policy("build.c++.modules.tryreuse") + target:policy("build.c++.modules.tryreuse") local reused = {} for sourcefile, fileconfig in pairs(from_depmodules) do if reuse and fileconfig.from_dep then local nocheck = target:policy("build.c++.modules.reuse.nocheck") local strict = target:policy("build.c++.modules.reuse.strict") or - target:policy("build.c++.modules.tryreuse.discriminate_on_defines") + target:policy("build.c++.modules.tryreuse.discriminate_on_defines") local dep = target:dep(fileconfig.from_dep) assert(dep, "dep target <%s> for <%s> not found", fileconfig.from_dep, target:fullname()) local can_reuse = nocheck or _are_flags_compatible(target, dep, sourcefile, {strict = strict}) @@ -415,7 +414,7 @@ function _patch_sourcebatch(target, sourcebatch) end table.sort(sourcebatch.sourcefiles) - memcache:set2(target:fullname(), "cached_sourcebatch", sourcebatch) + memcache:set2(target:fullname(), "cached_sourcebatch", table.clone(sourcebatch)) local keys = #sourcebatch.sourcefiles > 0 and table.concat(sourcebatch.sourcefiles) or "_" local sum = hash.strhash64(keys) @@ -566,7 +565,7 @@ function _schedule_module_dependencies_scan(target, jobgraph, sourcebatch) modules[name].alias = true end end - end) + end) local reused, from = support.is_reused(target, sourcefile) if reused then local scanfilejob = get_scanfilejob_for(from, sourcefile) @@ -886,9 +885,7 @@ function after_scan(target) local sourcebatches = target:sourcebatches() local sourcebatch_builder = sourcebatches and sourcebatches["c++.build.modules.builder"] local sourcebatch_scanner = sourcebatches and sourcebatches["c++.build.modules.scanner"] - if sourcebatch_scanner then - sourcebatch_scanner.sourcefiles = {} - end + support.memcache():set2(target:fullname(), "jobdeps", nil) if sourcebatch_builder then sourcebatch_builder.sourcefiles = {} sourcebatch_builder.dependfiles = {} diff --git a/xmake/rules/c++/modules/support.lua b/xmake/rules/c++/modules/support.lua index 4db95cb18..0f89d4e98 100644 --- a/xmake/rules/c++/modules/support.lua +++ b/xmake/rules/c++/modules/support.lua @@ -17,33 +17,34 @@ -- -- imports -import('core.base.bytes') -import('core.base.option') -import('core.base.json') -import('core.base.hashset') -import('core.cache.memcache', { alias = '_memcache' }) -import('core.cache.localcache', { alias = '_localcache' }) -import('core.tool.toolchain') -import('core.project.project') -import('core.project.config') -import('lib.detect.find_file') -import('async.runjobs') - -function _support(target) return import_implementation_of(target, 'support') end +import("core.base.bytes") +import("core.base.option") +import("core.base.json") +import("core.base.hashset") +import("core.cache.memcache", {alias = "_memcache"}) +import("core.cache.localcache", {alias = "_localcache"}) +import("async.runjobs") +import("lib.detect.find_file") +import("core.project.project") +import("core.project.config") + +function _support(target) + return import_implementation_of(target, "support") +end function import_implementation_of(target, name) local cachekey = tostring(target) local implementation = memcache():get2(name, cachekey) if implementation == nil then - if target:has_tool('cxx', 'clang', 'clangxx', 'clang_cl') then - implementation = import('clang.' .. name, { anonymous = true }) - elseif target:has_tool('cxx', 'gcc', 'gxx') then - implementation = import('gcc.' .. name, { anonymous = true }) - elseif target:has_tool('cxx', 'cl') then - implementation = import('msvc.' .. name, { anonymous = true }) + if target:has_tool("cxx", "clang", "clangxx", "clang_cl") then + implementation = import("clang." .. name, {anonymous = true}) + elseif target:has_tool("cxx", "gcc", "gxx") then + implementation = import("gcc." .. name, {anonymous = true}) + elseif target:has_tool("cxx", "cl") then + implementation = import("msvc." .. name, {anonymous = true}) else - local _, toolname = target:tool('cxx') - raise('compiler(%s): does not implementation c++ module!', toolname) + local _, toolname = target:tool("cxx") + raise("compiler(%s): does not implementation c++ module!", toolname) end memcache():set2(name, cachekey, implementation) end @@ -53,70 +54,62 @@ end -- load module support for the current target function load(target) -- At least std c++20 is required, and we should call `set_languages("c++20")` to set it - local languages = target:get('languages') + local languages = target:get("languages") local cxxlang = false for _, lang in ipairs(languages) do - if - lang:find('cxx', 1, true) - or lang:find('c++', 1, true) - or lang:find('gnuxx', 1, true) - or lang:find('gnu++', 1, true) - then + if lang:find("cxx", 1, true) or lang:find("c++", 1, true) or lang:find("gnuxx", 1, true) or lang:find("gnu++", 1, true) then cxxlang = true break end end - if not cxxlang then target:add('languages', 'c++20') end + if not cxxlang then + target:add("languages", "c++20") + end -- load module support for the specific compiler _support(target).load(target) end function get_cpplibrary_name(target) -- libc++ come first because on windows, if we use libc++ clang will still use msvc crt so MD / MT / MDd / MTd can be set - if target:has_runtime('c++_shared', 'c++_static') then - return 'c++' - elseif target:has_runtime('stdc++_shared', 'stdc++_static', 'gnustl_static', 'gnustl_shared') then - return 'stdc++' - elseif target:has_runtime('stlport_static', 'stlport_shared') then - return 'stlport' - elseif target:has_runtime('MD', 'MT', 'MDd', 'MTd') then - return 'msstl' + if target:has_runtime("c++_shared", "c++_static") then + return "c++" + elseif target:has_runtime("stdc++_shared", "stdc++_static") then + return "stdc++" + elseif target:has_runtime("MD", "MT", "MDd", "MTd") then + return "msstl" end -- if no specified runtime, fallback on native platform C++ library - if target:is_plat('android') then - local ndk = toolchain.load('ndk', { plat = plat, arch = arch }) - local ver = ndk:config('ndkver') - if not ver or ver > 14 then - return 'c++' - else - return 'stdc++' - end - elseif target:is_plat('macosx', 'iphoneos', 'watchos', 'appletvos', 'applexros', 'bsd', 'harmony') then - return 'c++' - elseif target:is_plat('linux', 'mingw', 'cygwin', 'msys', 'haiku') then - return 'stdc++' - elseif target:is_plat('windows') then - return 'msstl' + if target:is_plat("macosx", "iphoneos", "appletvos") then + return "c++" + elseif target:is_plat("linux") or target:is_plat("mingw") then + return "stdc++" + elseif target:is_plat("windows") then + return "msstl" end end -function has_two_phase_compilation_support(target) return _support(target).has_two_phase_compilation_support(target) end +function has_two_phase_compilation_support(target) + return _support(target).has_two_phase_compilation_support(target) +end -- strip flags not relevent for module reuse function strip_flags(target, flags, opt) + local strippeable_flags, splitted_strippeable_flags if not opt.requiresonly then - strippeable_flags, splitted_strippeable_flags = _support(target).strippeable_flags() - if opt and opt.strip_defines then table.join2(splitted_strippeable_flags, { 'D', 'U' }) end + strippeable_flags, splitted_strippeable_flags = _support(target).strippeable_flags() + if opt and opt.strip_defines then + table.join2(splitted_strippeable_flags, {"D", "U"}) + end else - strippeable_flags, splitted_strippeable_flags = _support(target).require_flags() + strippeable_flags, splitted_strippeable_flags = _support(target).require_flags() end local splitted_strippeable_flags_set = hashset.new() for _, flag in ipairs(splitted_strippeable_flags) do table.insert(strippeable_flags, flag) - splitted_strippeable_flags_set:insert('/' .. flag) - splitted_strippeable_flags_set:insert('-' .. flag) + splitted_strippeable_flags_set:insert("/" .. flag) + splitted_strippeable_flags_set:insert("-" .. flag) end local output = {} @@ -129,26 +122,28 @@ function strip_flags(target, flags, opt) strip_next_flag = false else for _, _flag in ipairs(strippeable_flags) do - if (flag == '/' .. _flag) or (flag == '-' .. _flag) then + if (flag == "/" .. _flag) or (flag == "-" .. _flag) then strip = true strip_next_flag = splitted_strippeable_flags_set:has(flag) break - elseif flag:startswith('/' .. _flag) or flag:startswith('-' .. _flag) then + elseif flag:startswith("/" .. _flag) or flag:startswith("-" .. _flag) then strip = true break end end end - if not strip then table.insert(output, flag) end + if not strip then + table.insert(output, flag) + end end return output end -- extract defines from flags function get_headerunit_key(target, sourcefile) - local defines = target:get('defines') or {} - local undefines = target:get('undefines') or {} + local defines = target:get("defines") or {} + local undefines = target:get("undefines") or {} local fileconfig = target:fileconfig(sourcefile) if fileconfig then table.join(defines, fileconfig.defines or {}) @@ -156,14 +151,14 @@ function get_headerunit_key(target, sourcefile) end if #defines > 0 then - defines = table.concat(defines, '-D') + defines = table.concat(defines, "-D") else - defines = '' + defines = "" end if #undefines > 0 then - undefines = table.concat(undefines, '-D') + undefines = table.concat(undefines, "-D") else - undefines = '' + undefines = "" end local key = hash.md5(bytes(defines .. undefines)) @@ -171,12 +166,14 @@ function get_headerunit_key(target, sourcefile) end -- get bmi extension -function get_bmi_extension(target) return _support(target).get_bmi_extension() end +function get_bmi_extension(target) + return _support(target).get_bmi_extension() +end -- get bmi path -- @see https://github.com/xmake-io/xmake/issues/4063 function get_bmi_path(bmifile) - bmifile = bmifile:gsub(':', '_PARTITION_') + bmifile = bmifile:gsub(":", "_PARTITION_") return path.normalize(bmifile) end @@ -185,7 +182,7 @@ function has_module_extension(sourcefile, opt) opt = opt or {} local modulexts = _g.modulexts if modulexts == nil then - modulexts = hashset.of('.cppm', '.ccm', '.cxxm', '.c++m', '.mpp', '.mxx', '.ixx') + modulexts = hashset.of(".cppm", ".ccm", ".cxxm", ".c++m", ".mpp", ".mxx", ".ixx") _g.modulexts = modulexts end local extension = opt.extension or path.extension(sourcefile) @@ -195,12 +192,14 @@ end -- this target contains module files? function contains_modules(target) -- we can not use `"c++.build.modules.builder"`, because it contains sourcekind/cxx. - local target_with_modules = target:sourcebatches()['c++.build.modules'] and true or false - if not target_with_modules then target_with_modules = target:policy('build.c++.modules') end + local target_with_modules = target:sourcebatches()["c++.build.modules"] and true or false + if not target_with_modules then + target_with_modules = target:policy("build.c++.modules") + end if not target_with_modules then for _, dep in ipairs(target:orderdeps()) do local sourcebatches = dep:sourcebatches() - if sourcebatches['c++.build.modules'] then + if sourcebatches["c++.build.modules"] then target_with_modules = true break end @@ -211,15 +210,15 @@ end -- mark that a module scan artifacts and bmifile are reused from an other target function set_reused(target, from, sourcefile) - memcache():set2(target:fullname() .. '/modules/' .. sourcefile, 'reuse', from) - if option.get('diagnosis') then - print('<' .. target:fullname() .. '>', 'reuse', sourcefile, 'from', '<' .. from:fullname() .. '>') + memcache():set2(target:fullname() .. "/modules/" .. sourcefile, "reuse", from) + if option.get("diagnosis") then + print("<" .. target:fullname() .. ">", "reuse", sourcefile, "from", "<" .. from:fullname() .. ">") end end -- query if a module scan artifacts and bmifile are reused from an other target function is_reused(target, sourcefile) - local from = memcache():get2(target:fullname() .. '/modules/' .. sourcefile, 'reuse') + local from = memcache():get2(target:fullname() .. "/modules/" .. sourcefile, "reuse") return from and true or false, from end @@ -249,16 +248,20 @@ end -- query if a module can be culled function can_be_culled(target, sourcefile) - local can_cull = target:policy('build.c++.modules.culling') + local can_cull = target:policy("build.c++.modules.culling") local fileconfig = target:fileconfig(sourcefile) local _, stdmodules_set = get_stdmodules(target) local is_stdmodule = stdmodules_set and stdmodules_set:has(sourcefile) or false - local public = target:kind() == 'moduleonly' and not is_stdmodule + local public = target:kind() == "moduleonly" and not is_stdmodule if fileconfig then public = fileconfig.public - if fileconfig.cull ~= nil then can_cull = can_cull and fileconfig.cull end + if fileconfig.cull ~= nil then + can_cull = can_cull and fileconfig.cull + end + end + if can_cull then + can_cull = is_stdmodule or (fileconfig and fileconfig.external) end - if can_cull then can_cull = is_stdmodule or (fileconfig and fileconfig.external) end return can_cull and not public end @@ -279,93 +282,106 @@ end function find_quote_header_file(sourcefile, file) local p = path.join(path.directory(path.absolute(sourcefile, project.directory())), file) - assert(os.isfile(p), '"%s" not found', p) + assert(os.isfile(p), "\"%s\" not found", p) return p end function find_angle_header_file(target, file) local headerpaths = _support(target).toolchain_includedirs(target) for _, dep in ipairs(target:orderdeps()) do - local includedirs = table.join(dep:get('sysincludedirs') or {}, dep:get('includedirs') or {}) + local includedirs = table.join(dep:get("sysincludedirs") or {}, dep:get("includedirs") or {}) table.join2(headerpaths, includedirs) end for _, pkg in ipairs(target:orderpkgs()) do - local includedirs = table.join(pkg:get('sysincludedirs') or {}, pkg:get('includedirs') or {}) + local includedirs = table.join(pkg:get("sysincludedirs") or {}, pkg:get("includedirs") or {}) table.join2(headerpaths, includedirs) end - table.join2(headerpaths, target:get('includedirs')) + table.join2(headerpaths, target:get("includedirs")) local p = find_file(file, headerpaths) - assert(p, '<%s> not found!', file) + assert(p, "<%s> not found!", file) return p end -- get stdmodules function get_stdmodules(target) local cpplib = get_cpplibrary_name(target) - local stdmodules = memcache():get2(cpplib, 'c++.modules.stdmodules') - local stdmodules_set = memcache():get2(cpplib, 'c++.modules.stdmodules_set') + local stdmodules = memcache():get2(cpplib, "c++.modules.stdmodules") + local stdmodules_set = memcache():get2(cpplib, "c++.modules.stdmodules_set") if not stdmodules or not stdmodules_set then stdmodules = _support(target).get_stdmodules(target) stdmodules_set = hashset.from(stdmodules or {}) - memcache():set2(cpplib, 'c++.modules.stdmodules', stdmodules) - memcache():set2(cpplib, 'c++.modules.stdmodules_set', stdmodules_set) + memcache():set2(cpplib, "c++.modules.stdmodules", stdmodules) + memcache():set2(cpplib, "c++.modules.stdmodules_set", stdmodules_set) end return stdmodules, stdmodules_set end -- get memcache -function memcache() return _memcache.cache('cxxmodules') end +function memcache() + return _memcache.cache("cxxmodules") +end -- get localcache -function localcache() return _localcache.cache('cxxmodules') end +function localcache() + return _localcache.cache("cxxmodules") +end -- get modules cache directory function modules_cachedir(target, opt) assert(opt and (opt.interface ~= nil or opt.headerunit or opt.scan)) local moduletype if opt.headerunit then - moduletype = 'headerunits' + moduletype = "headerunits" elseif opt.interface then - moduletype = 'interfaces' + moduletype = "interfaces" elseif opt.scan then - moduletype = 'scans' + moduletype = "scans" else - moduletype = 'implementation' + moduletype = "implementation" + end + local cachedir = path.join(target:autogendir(), "rules", "bmi", "cache", moduletype) + if opt.mkdir and not os.isdir(cachedir) then + os.mkdir(cachedir) end - local cachedir = path.join(target:autogendir(), 'rules', 'bmi', 'cache', moduletype) - if opt.mkdir and not os.isdir(cachedir) then os.mkdir(cachedir) end return cachedir end -function get_modulehash(sourcefile) return hash.strhash64(sourcefile) end +function get_modulehash(sourcefile) + return hash.strhash64(sourcefile) +end function get_metafile(target, module) -- metafile are only for named modules - local outputdir = get_outputdir(target, module.sourcefile, { interface = module.interface or false }) - return path.join(outputdir, path.filename(module.sourcefile) .. '.meta-info') + local outputdir = get_outputdir(target, module.sourcefile, {interface = module.interface or false}) + return path.join(outputdir, path.filename(module.sourcefile) .. ".meta-info") end function get_outputdir(target, sourcefile, opt) local cachedir = modules_cachedir(target, opt) local modulehash = opt.key or get_modulehash(sourcefile) local outputdir = path.join(cachedir, modulehash) - if not os.exists(outputdir) then os.mkdir(outputdir) end + if not os.exists(outputdir) then + os.mkdir(outputdir) + end return outputdir end function add_installfiles_for_modules(target, modules) - local sourcebatch = target:sourcebatches()['c++.build.modules.install'] + local sourcebatch = target:sourcebatches()["c++.build.modules.install"] if sourcebatch and sourcebatch.sourcefiles then for _, sourcefile in ipairs(sourcebatch.sourcefiles) do local fileconfig = target:fileconfig(sourcefile) local install = fileconfig and fileconfig.public or false if install then local modulehash = get_modulehash(sourcefile) - local prefixdir = path.join('modules', modulehash) - target:add('installfiles', sourcefile, { prefixdir = prefixdir }) + local prefixdir = path.join("modules", modulehash) + target:add("installfiles", sourcefile, {prefixdir = prefixdir}) local metafile = get_metafile(target, modules[sourcefile]) - if os.exists(metafile) then target:add('installfiles', metafile, { prefixdir = prefixdir }) end + if os.exists(metafile) then + target:add("installfiles", metafile, {prefixdir = prefixdir}) + end end end end end + -- cgit v1.3.1