summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArthur LAURENT <[email protected]>2025-11-04 12:36:33 +0100
committerArthur LAURENT <[email protected]>2025-11-04 12:36:33 +0100
commitce723897432dad8d8f94ddf831e3a8d7db6e2700 (patch)
tree2f2b90ec77ff0cf6093f2e534e69d9e9eda116dc
parent791bb25721f76b49b384b6f8657bac7660036b4a (diff)
fix module reusage with xmake test
-rw-r--r--tests/projects/c++/modules/test_xmake_test.lua3
-rw-r--r--xmake/rules/c++/modules/builder.lua11
-rw-r--r--xmake/rules/c++/modules/scanner.lua13
-rw-r--r--xmake/rules/c++/modules/support.lua224
4 files changed, 132 insertions, 119 deletions
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')
+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 _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 = '<NO_DEFINES>'
+ defines = "<NO_DEFINES>"
end
if #undefines > 0 then
- undefines = table.concat(undefines, '-D')
+ undefines = table.concat(undefines, "-D")
else
- undefines = '<NO_UNDEFINES>'
+ undefines = "<NO_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
+