diff options
| author | ruki <[email protected]> | 2024-02-14 21:47:55 +0800 |
|---|---|---|
| committer | GitHub <[email protected]> | 2024-02-14 21:47:55 +0800 |
| commit | b1d3ab468f9fefaed3368d0a9bb90fb4f9c0807b (patch) | |
| tree | 0814552f84537cf2e15e5d5b48ac324cd7dd5e85 | |
| parent | a7396842b55fb3d9747b5da199d6d5ec1afb8aa9 (diff) | |
| parent | 45b8fa3d0f456e820e6fdd38c7e000fa52064b4d (diff) | |
Merge pull request #4728 from Arthapz/reuse-modules
implement module reusage based on flag comparison
13 files changed, 517 insertions, 106 deletions
diff --git a/tests/projects/c++/modules/private_module/src/use.cpp b/tests/projects/c++/modules/private_module/src/use.cpp new file mode 100644 index 000000000..f71a3057e --- /dev/null +++ b/tests/projects/c++/modules/private_module/src/use.cpp @@ -0,0 +1,8 @@ +module use; + +import dep1; +import dep2; + +int lib() { + return m() + i(); +} diff --git a/tests/projects/c++/modules/private_module/src/use.mpp b/tests/projects/c++/modules/private_module/src/use.mpp index 8450c341c..33f90c14a 100644 --- a/tests/projects/c++/modules/private_module/src/use.mpp +++ b/tests/projects/c++/modules/private_module/src/use.mpp @@ -1,6 +1,3 @@ -import dep1; -import dep2; +export module use; -int lib() { - return m() + i(); -}
\ No newline at end of file +export int lib(); diff --git a/tests/projects/c++/modules/private_module/xmake.lua b/tests/projects/c++/modules/private_module/xmake.lua index 57ede6993..16a2bea92 100644 --- a/tests/projects/c++/modules/private_module/xmake.lua +++ b/tests/projects/c++/modules/private_module/xmake.lua @@ -5,3 +5,4 @@ target("private_module") add_rules("c++") set_kind("$(kind)") add_files("src/*.mpp") + add_files("src/*.cpp") diff --git a/xmake/core/project/policy.lua b/xmake/core/project/policy.lua index ebdc3d4dd..64fc23883 100644 --- a/xmake/core/project/policy.lua +++ b/xmake/core/project/policy.lua @@ -66,6 +66,10 @@ function policy.policies() ["build.c++.modules"] = {description = "Enable C++ modules for C++ building.", type = "boolean"}, -- Enable std module ["build.c++.modules.std"] = {description = "Enable std modules.", default = true, type = "boolean"}, + -- Try to reuse compiled module bmi file if targets flags permit it + ["build.c++.modules.tryreuse"] = {description = "Try to reuse compiled module if possible.", default = true, type = "boolean"}, + -- Enable module taking defines acbount for bmi reuse discrimination + ["build.c++.modules.tryreuse.discriminate_on_defines"] = {description = "Enable defines module reuse discrimination.", default = false, type = "boolean"}, -- Force C++ modules fallback dependency scanner for clang ["build.c++.clang.fallbackscanner"] = {description = "Force clang fallback module dependency scanner.", default = false, type = "boolean"}, -- Force C++ modules fallback dependency scanner for msvc diff --git a/xmake/rules/c++/modules/modules_support/builder.lua b/xmake/rules/c++/modules/modules_support/builder.lua index 936f90414..a33f69261 100644 --- a/xmake/rules/c++/modules/modules_support/builder.lua +++ b/xmake/rules/c++/modules/modules_support/builder.lua @@ -33,7 +33,6 @@ import("dependency_scanner") -- build target modules function _build_modules(target, sourcebatch, modules, opt) local objectfiles = sourcebatch.objectfiles - _builder(target).populate_module_map(target, modules) -- build modules for _, objectfile in ipairs(objectfiles) do @@ -81,16 +80,79 @@ function _build_headerunits(target, headerunits, opt) end end +-- check if flags are compatible for module reuse +function _are_flags_compatible(target, other, cppfile) + local compinst1 = target:compiler("cxx") + local flags1 = compinst1:compflags({sourcefile = cppfile, target = target}) + + local compinst2 = other:compiler("cxx") + local flags2 = compinst2:compflags({sourcefile = cppfile, target = other}) + + -- strip unrelevent flags + flags1 = compiler_support.strip_flags(target, flags1) + flags2 = compiler_support.strip_flags(target, flags2) + + if #flags1 ~= #flags2 then + return false + end + + table.sort(flags1) + table.sort(flags2) + + for i = 1, #flags1 do + if flags1[i] ~= flags2[i] then + return false + end + end + + return true +end + +-- try to reuse modules from other target +function _try_reuse_modules(target, modules) + for _, module in pairs(modules) do + local name, provide, cppfile = compiler_support.get_provided_module(module) + if not provide then + goto CONTINUE + end + + cppfile = cppfile or module.cppfile + + local fileconfig = target:fileconfig(cppfile) + local public = fileconfig and (fileconfig.public or fileconfig.external) + if not public then + goto CONTINUE + end + + for _, dep in ipairs(target:orderdeps()) do + if not _are_flags_compatible(target, dep, cppfile) then + goto NEXT + end + local mapped = get_from_target_mapper(dep, name) + if mapped then + compiler_support.memcache():set2(target:name() .. name, "reuse", true) + add_module_to_target_mapper(target, mapped.name, mapped.sourcefile, mapped.bmi, table.join(mapped.opt or {}, {target = dep})) + break + end + ::NEXT:: + end + + ::CONTINUE:: + end + return modules +end + -- should we build this module or headerunit ? function should_build(target, sourcefile, bmifile, opt) -- force rebuild a module if any of its module dependency is rebuilt - local requires = opt.requires + local requires = opt and opt.requires if requires then for required, _ in table.orderpairs(requires) do local m = get_from_target_mapper(target, required) if m then - local rebuild = compiler_support.memcache():get2("should_build_in" .. target:name(), m.key) + local rebuild = (m.opt and m.opt.target) and compiler_support.memcache():get2("should_build_in_" .. m.opt.target:name(), m.key) + or compiler_support.memcache():get2("should_build_in_" .. target:name(), m.key) if rebuild then return true end @@ -98,6 +160,14 @@ function should_build(target, sourcefile, bmifile, opt) end end + -- reused + if opt and opt.name then + local m = get_from_target_mapper(target, opt.name) + if m and m.opt and m.opt.target then + return compiler_support.memcache():get2("should_build_in_" .. m.opt.target:name(), m.key) + end + end + -- or rebuild it if the file changed local objectfile = opt.objectfile local dryrun = option.get("dry-run") @@ -175,7 +245,7 @@ function _builder(target) end function mark_build(target, name) - compiler_support.memcache():set2("should_build_in" .. target:name(), name, true) + compiler_support.memcache():set2("should_build_in_" .. target:name(), name, true) end -- build batchjobs for modules @@ -189,6 +259,11 @@ function build_modules_for_batchjobs(target, batchjobs, sourcebatch, modules, op opt.rootjob = batchjobs:group_leave() or opt.rootjob batchjobs:group_enter(target:name() .. "/build_modules", {rootjob = opt.rootjob}) + batchjobs:addjob(target:name() .. "_populate_module_map", function(_, _) + _try_reuse_modules(target, modules) + _builder(target).populate_module_map(target, modules) + end, {rootjob = opt.rootjob}) + local modulesjobs = {} _build_modules(target, sourcebatch, modules, table.join(opt, { build_module = function(deps, module, name, objectfile, cppfile) @@ -208,6 +283,9 @@ function build_modules_for_batchcmds(target, batchcmds, sourcebatch, modules, op local depmtime = 0 opt.progress = opt.progress or 0 + _try_reuse_modules(target, modules) + _builder(target).populate_module_map(target, modules) + -- build modules _build_modules(target, sourcebatch, modules, table.join(opt, { build_module = function(_, module, _, objectfile, cppfile) @@ -352,7 +430,9 @@ end -- add a module to target mapper function add_module_to_target_mapper(target, name, sourcefile, bmifile, opt) local mapper = get_target_module_mapper(target) - mapper[name] = {name = name, key = name, bmi = bmifile, sourcefile = sourcefile, opt = opt} + if not mapper[name] then + mapper[name] = {name = name, key = name, bmi = bmifile, sourcefile = sourcefile, opt = opt} + end flush_target_module_mapper_keys(target) end diff --git a/xmake/rules/c++/modules/modules_support/clang/builder.lua b/xmake/rules/c++/modules/modules_support/clang/builder.lua index 0e5f0b5bd..05835b0e1 100644 --- a/xmake/rules/c++/modules/modules_support/clang/builder.lua +++ b/xmake/rules/c++/modules/modules_support/clang/builder.lua @@ -30,34 +30,38 @@ import("core.project.depend") import("compiler_support") import(".builder", {inherit = true}) --- get flags for building a module -function _make_modulebuildflags(target, provide, bmifile, opt) - +function _compile_one_step(target, bmifile, sourcefile, objectfile, opt) -- get flags local module_outputflag = compiler_support.get_moduleoutputflag(target) - - local flags - local precompile = false - if module_outputflag and provide and opt.build_objectfile then -- one step compilation of named module, clang >= 16 - flags = {{"-x", "c++-module", module_outputflag .. bmifile}} - elseif provide then -- two step compilation of named module - precompile = true - flags = {{"-x", "c++-module", "--precompile"}} - if opt.build_objectfile then - table.insert(flags, {}) + if module_outputflag then + local flags = table.join({"-x", "c++-module", module_outputflag .. bmifile}, opt.std and {"-Wno-include-angled-in-module-purview", "-Wno-reserved-module-identifier"} or {}) + if opt and opt.batchcmds then + _batchcmds_compile(opt.batchcmds, target, flags, sourcefile, objectfile) + else + _compile(target, flags, sourcefile, objectfile) end - else -- internal module, no bmi needed - flags = {{"-x", "c++"}} + else + _compile_bmi_step(target, bmifile, sourcefile, opt) + _compile_objectfile_step(target, bmifile, sourcefile, objectfile, opt) end +end - if opt.name == "std" or opt.name == "std.compat" then - table.join2(flags[1], {"-Wno-include-angled-in-module-purview", "-Wno-reserved-module-identifier"}) - if flags[2] then - table.join2(flags[2], {"-Wno-include-angled-in-module-purview", "-Wno-reserved-module-identifier"}) - end +function _compile_bmi_step(target, bmifile, sourcefile, opt) + local flags = table.join({"-x", "c++-module", "--precompile"}, opt.std and {"-Wno-include-angled-in-module-purview", "-Wno-reserved-module-identifier"} or {}) + if opt and opt.batchcmds then + _batchcmds_compile(opt.batchcmds, target, flags, sourcefile, bmifile) + else + _compile(target, flags, sourcefile, bmifile) end +end - return precompile, table.unpack(flags) +function _compile_objectfile_step(target, bmifile, sourcefile, objectfile, opt) + _compile(target, {}, sourcefile, objectfile, {bmifile = bmifile}) + if opt and opt.batchcmds then + _batchcmds_compile(opt.batchcmds, target, {}, sourcefile, objectfile, {bmifile = bmifile}) + else + _compile(target, {}, sourcefile, objectfile, {bmifile = bmifile}) + end end -- get flags for building a headerunit @@ -198,16 +202,24 @@ function make_module_buildjobs(target, batchjobs, job_name, deps, opt) return { name = job_name, - deps = deps, + deps = table.join(target:name() .. "_populate_module_map", deps), sourcefile = opt.cppfile, job = batchjobs:newjob(name or opt.cppfile, function(index, total) + local mapped_bmi + if provide and compiler_support.memcache():get2(target:name() .. name, "reuse") then + if not target:is_binary() then + return + else + mapped_bmi = get_from_target_mapper(target, name).bmi + end + end local compinst = compiler.load("cxx", {target = target}) local compflags = compinst:compflags({sourcefile = opt.cppfile, target = target}) local build if provide or compiler_support.has_module_extension(opt.cppfile) then - build = should_build(target, opt.cppfile, bmifile, {objectfile = opt.objectfile, requires = opt.module.requires}) + build = should_build(target, opt.cppfile, bmifile, {name = name, objectfile = opt.objectfile, requires = opt.module.requires}) -- needed to detect rebuild of dependencies if provide and build then @@ -222,7 +234,7 @@ function make_module_buildjobs(target, batchjobs, job_name, deps, opt) -- for cpp file we need to check after appendings the flags if build == nil then - build = should_build(target, opt.cppfile, bmifile, {objectfile = opt.objectfile, requires = opt.module.requires}) + build = should_build(target, opt.cppfile, bmifile, {name = name, objectfile = opt.objectfile, requires = opt.module.requires}) end local dependfile = target:dependfile(bmifile or opt.objectfile) @@ -233,8 +245,6 @@ function make_module_buildjobs(target, batchjobs, job_name, deps, opt) if build then -- compile if it's a named module if provide or compiler_support.has_module_extension(opt.cppfile) then - progress.show((index * 100) / total, "${color.build.target}<%s> ${clear}${color.build.object}compiling.module.$(mode) %s", target:name(), name or opt.cppfile) - if not dryrun then local objectdir = path.directory(opt.objectfile) if not os.isdir(objectdir) then @@ -242,17 +252,26 @@ function make_module_buildjobs(target, batchjobs, job_name, deps, opt) end end - local fileconfig = target:fileconfig(opt.cppfile) - local public = fileconfig and fileconfig.public - local external = fileconfig and fileconfig.external - local build_objectfile = target:kind() == "binary" or (not public and not external) - - local precompile, first_step, second_step = _make_modulebuildflags(target, provide, bmifile, {sourcefile = opt.cppfile, build_objectfile = build_objectfile, name = name}) - - _compile(target, first_step, opt.cppfile, precompile and bmifile or opt.objectfile) - - if second_step then - _compile(target, second_step, opt.cppfile, opt.objectfile, {bmifile = bmifile}) + local fileconfig = target:fileconfig(opt.cppfile) + local public = fileconfig and fileconfig.public + local external = fileconfig and fileconfig.external + local bmifile = mapped_bmi or bmifile + if target:is_binary() then + if mapped_bmi then + progress.show((index * 100) / total, "${color.build.target}<%s> ${clear}${color.build.object}compiling.objectfile.$(mode) %s", target:name(), name or opt.cppfile) + _compile_objectfile_step(target, bmifile, opt.cppfile, opt.objectfile) + else + progress.show((index * 100) / total, "${color.build.target}<%s> ${clear}${color.build.object}compiling.module.$(mode) %s", target:name(), name or opt.cppfile) + _compile_one_step(target, bmifile, opt.cppfile, opt.objectfile, {std = (name == "std" or name == "std.compat")}) + end + else + if not public and not external then + progress.show((index * 100) / total, "${color.build.target}<%s> ${clear}${color.build.object}compiling.module.$(mode) %s", target:name(), name or opt.cppfile) + _compile_one_step(target, bmifile, opt.cppfile, opt.objectfile, {std = (name == "std" or name == "std.compat")}) + else + progress.show((index * 100) / total, "${color.build.target}<%s> ${clear}${color.build.object}compiling.bmi.$(mode) %s", target:name(), name or opt.cppfile) + _compile_bmi_step(target, bmifile, opt.cppfile, {std = (name == "std" or name == "std.compat")}) + end end else os.tryrm(opt.objectfile) -- force rebuild for .cpp files @@ -271,9 +290,18 @@ function make_module_buildcmds(target, batchcmds, opt) local name, provide, _ = compiler_support.get_provided_module(opt.module) local bmifile = provide and compiler_support.get_bmi_path(provide.bmi) + local mapped_bmi + if provide and compiler_support.memcache():get2(target:name() .. name, "reuse") then + if not target:is_binary() then + return + else + mapped_bmi = get_from_target_mapper(target, name).bmi + end + end + local build if provide or compiler_support.has_module_extension(opt.cppfile) then - build = should_build(target, opt.cppfile, bmifile, {objectfile = opt.objectfile, requires = opt.module.requires}) + build = should_build(target, opt.cppfile, bmifile, {name = name, objectfile = opt.objectfile, requires = opt.module.requires}) -- needed to detect rebuild of dependencies if provide and build then @@ -288,26 +316,34 @@ function make_module_buildcmds(target, batchcmds, opt) -- for cpp file we need to check after appendings the flags if build == nil then - build = should_build(target, opt.cppfile, bmifile, {objectfile = opt.objectfile, requires = opt.module.requires}) + build = should_build(target, opt.cppfile, bmifile, {name = name, objectfile = opt.objectfile, requires = opt.module.requires}) end - local build_objectfile = target:kind() == "binary" if build then -- compile if it's a named module if provide or compiler_support.has_module_extension(opt.cppfile) then - batchcmds:show_progress(opt.progress, "${color.build.target}<%s> ${clear}${color.build.object}compiling.module.$(mode) %s", target:name(), name or opt.cppfile) batchcmds:mkdir(path.directory(opt.objectfile)) local fileconfig = target:fileconfig(opt.cppfile) local public = fileconfig and fileconfig.public local external = fileconfig and fileconfig.external - local build_objectfile = target:kind() == "binary" or (not public and not external) - - local precompile, first_step, second_step = _make_modulebuildflags(target, provide, bmifile, {batchcmds = true, sourcefile = opt.cppfile, build_objectfile = build_objectfile, name = name}) - _batchcmds_compile(batchcmds, target, first_step, opt.cppfile, precompile and bmifile or opt.objectfile) - - if second_step then - _batchcmds_compile(batchcmds, target, second_step, opt.cppfile, opt.objectfile, {bmifile = bmifile}) + local bmifile = mapped_bmi or bmifile + if target:is_binary() then + if mapped_bmi then + batchcmds:show_progress(opt.progress, "${color.build.target}<%s> ${clear}${color.build.object}compiling.objectfile.$(mode) %s", target:name(), name or opt.cppfile) + _compile_objectfile_step(target, bmifile, opt.cppfile, opt.objectfile, {batchcmds = batchcmds}) + else + batchcmds:show_progress(opt.progress, "${color.build.target}<%s> ${clear}${color.build.object}compiling.module.$(mode) %s", target:name(), name or opt.cppfile) + _compile_one_step(target, bmifile, opt.cppfile, opt.objectfile, {std = (name == "std" or name == "std.compat"), batchcmds = batchcmds}) + end + else + if not public and not external then + batchcmds:show_progress(opt.progress, "${color.build.target}<%s> ${clear}${color.build.object}compiling.module.$(mode) %s", target:name(), name or opt.cppfile) + _compile_one_step(target, bmifile, opt.cppfile, opt.objectfile, {std = (name == "std" or name == "std.compat"), batchcmds = batchcmds}) + else + batchcmds:show_progress(opt.progress, "${color.build.target}<%s> ${clear}${color.build.object}compiling.bmi.$(mode) %s", target:name(), name or opt.cppfile) + _compile_bmi_step(target, bmifile, opt.cppfile, {std = (name == "std" or name == "std.compat"), batchcmds = batchcmds}) + end end else batchcmds:rm(opt.objectfile) -- force rebuild for .cpp files diff --git a/xmake/rules/c++/modules/modules_support/clang/compiler_support.lua b/xmake/rules/c++/modules/modules_support/clang/compiler_support.lua index 9f581cdca..49e01db90 100644 --- a/xmake/rules/c++/modules/modules_support/clang/compiler_support.lua +++ b/xmake/rules/c++/modules/modules_support/clang/compiler_support.lua @@ -107,6 +107,41 @@ function load(target) end end +-- strip flags that doesn't affect bmi generation +function strip_flags(target, flags) + -- speculative list as there is no resource that list flags that prevent reusability, this list will likely be improve over time + -- @see https://clang.llvm.org/docs/StandardCPlusPlusModules.html#consistency-requirement + local strippable_flags = { + "-I", + "-isystem", + "-g", + "-O", + "-W", + "-w", + "-cxx-isystem", + "-Q", + } + if not target:policy("build.c++.modules.tryreuse.discriminate_on_defines") then + table.join2(strippable_flags, {"-D", "-U"}) + end + local output = {} + local last_flag_I = false + for _, flag in ipairs(flags) do + local strip = false + for _, _flag in ipairs(strippable_flags) do + if flag:startswith(_flag) or last_flag_I then + last_flag_I = _flag == "-I" + strip = true + break + end + end + if not strip then + table.insert(output, flag) + end + end + return output +end + -- provide toolchain include directories for stl headerunit when p1689 is not supported function toolchain_includedirs(target) local includedirs = _g.includedirs diff --git a/xmake/rules/c++/modules/modules_support/compiler_support.lua b/xmake/rules/c++/modules/modules_support/compiler_support.lua index d11e833fd..9d3d4fadf 100644 --- a/xmake/rules/c++/modules/modules_support/compiler_support.lua +++ b/xmake/rules/c++/modules/modules_support/compiler_support.lua @@ -49,6 +49,11 @@ function load(target) _compiler_support(target).load(target) end +-- strip flags not relevent for module reuse +function strip_flags(target, flags) + return _compiler_support(target).strip_flags(target, flags) +end + -- patch sourcebatch function patch_sourcebatch(target, sourcebatch) sourcebatch.sourcekind = "cxx" diff --git a/xmake/rules/c++/modules/modules_support/dependency_scanner.lua b/xmake/rules/c++/modules/modules_support/dependency_scanner.lua index 0b02292ef..ef0e66cc6 100644 --- a/xmake/rules/c++/modules/modules_support/dependency_scanner.lua +++ b/xmake/rules/c++/modules/modules_support/dependency_scanner.lua @@ -178,7 +178,7 @@ function _get_edges(nodes, modules) for _, node in ipairs(nodes) do local module = modules[node] if module.requires then - for required_name, _ in pairs(module.requires) do + for required_name, _ in table.orderpairs(module.requires) do for _, required_node in ipairs(nodes) do local name, _, _ = compiler_support.get_provided_module(modules[required_node]) if name and name == required_name then diff --git a/xmake/rules/c++/modules/modules_support/gcc/builder.lua b/xmake/rules/c++/modules/modules_support/gcc/builder.lua index c4c9871e2..11eaf24c1 100644 --- a/xmake/rules/c++/modules/modules_support/gcc/builder.lua +++ b/xmake/rules/c++/modules/modules_support/gcc/builder.lua @@ -30,11 +30,6 @@ import("core.project.depend") import("compiler_support") import(".builder", {inherit = true}) --- get flags for building a module -function _make_modulebuildflags(target, opt) - return {"-x", "c++", "-c"} -end - -- get flags for building a headerunit function _make_headerunitflags(target, headerunit, headerunit_mapper) local module_headerflag = compiler_support.get_moduleheaderflag(target) @@ -99,7 +94,7 @@ end function _get_maplines(target, module) local maplines = {} - local m_name, m, cppfile = compiler_support.get_provided_module(module) + local m_name, m, _ = compiler_support.get_provided_module(module) if m then table.insert(maplines, m_name .. " " .. compiler_support.get_bmi_path(m.bmi)) end @@ -140,10 +135,10 @@ end -- /usr/include/c++/11/iostream build/.gens/stl_headerunit/linux/x86_64/release/stlmodules/cache/iostream.gcm -- hello build/.gens/stl_headerunit/linux/x86_64/release/rules/modules/cache/hello.gcm -- -function _generate_modulemapper_file(target, module) +function _generate_modulemapper_file(target, module, cppfile) local maplines = _get_maplines(target, module) - local path = os.tmpfile() - local mapper_file = io.open(path, "wb") + local mapper_path = path.join(os.tmpdir(), target:name():replace(" ", "_"), name or cppfile:replace(" ", "_")) + local mapper_file = io.open(mapper_path, "wb") mapper_file:write("root " .. os.projectdir():replace("\\", "/")) mapper_file:write("\n") for _, mapline in ipairs(maplines) do @@ -151,7 +146,7 @@ function _generate_modulemapper_file(target, module) mapper_file:write("\n") end mapper_file:close() - return path + return mapper_path end -- populate module map @@ -188,27 +183,35 @@ function make_module_buildjobs(target, batchjobs, job_name, deps, opt) return { name = job_name, - deps = deps, + deps = table.join(target:name() .. "_populate_module_map", deps), sourcefile = opt.cppfile, job = batchjobs:newjob(name or opt.cppfile, function(index, total) + local mapped_bmi + if provide and compiler_support.memcache():get2(target:name() .. name, "reuse") then + if not target:is_binary() then + return + else + mapped_bmi = get_from_target_mapper(target, name).bmi + end + end local compinst = compiler.load("cxx", {target = target}) local compflags = compinst:compflags({sourcefile = opt.cppfile, target = target}) - local build = should_build(target, opt.cppfile, bmifile, {objectfile = opt.objectfile, requires = opt.module.requires}) - - -- needed to detect rebuild of dependencies - if provide and build then - mark_build(target, name) - end - -- generate and append module mapper file local module_mapper if provide or opt.module.requires then - module_mapper = _generate_modulemapper_file(target, opt.module) + module_mapper = _generate_modulemapper_file(target, opt.module, opt.cppfile) target:fileconfig_add(opt.cppfile, {force = {cxxflags = {module_mapperflag .. module_mapper}}}) end + local build = should_build(target, opt.cppfile, bmifile, {name = name, objectfile = opt.objectfile, requires = opt.module.requires}) + + -- needed to detect rebuild of dependencies + if provide and build then + mark_build(target, name) + end + local dependfile = target:dependfile(bmifile or opt.objectfile) local dependinfo = depend.load(dependfile) or {} dependinfo.files = {} @@ -217,13 +220,35 @@ function make_module_buildjobs(target, batchjobs, job_name, deps, opt) if build then -- compile if it's a named module if provide or compiler_support.has_module_extension(opt.cppfile) then - progress.show((index * 100) / total, "${color.build.target}<%s> ${clear}${color.build.object}compiling.module.$(mode) %s", target:name(), name or opt.cppfile) + local fileconfig = target:fileconfig(opt.cppfile) + local public = fileconfig and fileconfig.public + local external = fileconfig and fileconfig.external + local bmifile = mapped_bmi or bmifile + local flags = {"-x", "c++"} + local sourcefile + if target:is_binary() then + if mapped_bmi then + progress.show((index * 100) / total, "${color.build.target}<%s> ${clear}${color.build.object}compiling.objectfile.$(mode) %s", target:name(), name or opt.cppfile) + sourcefile = bmifile + else + progress.show((index * 100) / total, "${color.build.target}<%s> ${clear}${color.build.object}compiling.module.$(mode) %s", target:name(), name or opt.cppfile) + sourcefile = opt.cppfile + end + else + if not public and not external then + progress.show((index * 100) / total, "${color.build.target}<%s> ${clear}${color.build.object}compiling.module.$(mode) %s", target:name(), name or opt.cppfile) + sourcefile = opt.cppfile + else + progress.show((index * 100) / total, "${color.build.target}<%s> ${clear}${color.build.object}compiling.bmi.$(mode) %s", target:name(), name or opt.cppfile) + local module_onlyflag = compiler_support.get_moduleonlyflag(target) + table.insert(flags, module_onlyflag) + sourcefile = opt.cppfile + end + end if option.get("diagnosis") then print("mapper file --------\n%s--------", io.readfile(module_mapper)) end - - local flags = _make_modulebuildflags(target, opt) - _compile(target, flags, opt.cppfile, opt.objectfile) + _compile(target, flags, sourcefile, opt.objectfile) os.tryrm(module_mapper) else os.tryrm(opt.objectfile) -- force rebuild for .cpp files @@ -242,29 +267,63 @@ function make_module_buildcmds(target, batchcmds, opt) local bmifile = provide and compiler_support.get_bmi_path(provide.bmi) local module_mapperflag = compiler_support.get_modulemapperflag(target) - local build = should_build(target, opt.cppfile, bmifile, {objectfile = opt.objectfile, requires = opt.module.requires}) - - -- needed to detect rebuild of dependencies - if provide and build then - mark_build(target, name) + local mapped_bmi + if provide and compiler_support.memcache():get2(target:name() .. name, "reuse") then + if not target:is_binary() then + return + else + mapped_bmi = get_from_target_mapper(target, name).bmi + end end -- generate and append module mapper file local module_mapper if provide or opt.module.requires then - module_mapper = _generate_modulemapper_file(target, opt.module) + module_mapper = _generate_modulemapper_file(target, opt.module, opt.cppfile) target:fileconfig_add(opt.cppfile, {force = {cxxflags = {module_mapperflag .. module_mapper}}}) end + local build = should_build(target, opt.cppfile, bmifile, {name = name, objectfile = opt.objectfile, requires = opt.module.requires}) + + -- needed to detect rebuild of dependencies + if provide and build then + mark_build(target, name) + end + if build then -- compile if it's a named module if provide or compiler_support.has_module_extension(opt.cppfile) then - batchcmds:show_progress(opt.progress, "${color.build.target}<%s> ${clear}${color.build.object}compiling.module.$(mode) %s", target:name(), name or opt.cppfile) + batchcmds:mkdir(path.directory(opt.objectfile)) + local fileconfig = target:fileconfig(opt.cppfile) + local public = fileconfig and fileconfig.public + local external = fileconfig and fileconfig.external + local bmifile = mapped_bmi or bmifile + local flags = {"-x", "c++"} + local sourcefile + if target:is_binary() then + if mapped_bmi then + batchcmds:show_progress(opt.progress, "${color.build.target}<%s> ${clear}${color.build.object}compiling.objectfile.$(mode) %s", target:name(), name or opt.cppfile) + sourcefile = bmifile + else + batchcmds:show_progress(opt.progress, "${color.build.target}<%s> ${clear}${color.build.object}compiling.module.$(mode) %s", target:name(), name or opt.cppfile) + sourcefile = opt.cppfile + end + else + if not public and not external then + batchcmds:show_progress(opt.progress, "${color.build.target}<%s> ${clear}${color.build.object}compiling.module.$(mode) %s", target:name(), name or opt.cppfile) + sourcefile = opt.cppfile + else + batchcmds:show_progress(opt.progress, "${color.build.target}<%s> ${clear}${color.build.object}compiling.bmi.$(mode) %s", target:name(), name or opt.cppfile) + local module_onlyflag = compiler_support.get_moduleonlyflag(target) + table.insert(flags, module_onlyflag) + sourcefile = opt.cppfile + end + end if option.get("diagnosis") then batchcmds:print("mapper file: %s", io.readfile(module_mapper)) end - batchcmds:mkdir(path.directory(opt.objectfile)) - _batchcmds_compile(batchcmds, target, _make_modulebuildflags(target, {batchcmds = true, sourcefile = opt.cppfile}), opt.cppfile, opt.objectfile) + _batchcmds_compile(batchcmds, target, flags, sourcefile, opt.objectfile) + batchcmds:rm(module_mapper) else batchcmds:rm(opt.objectfile) -- force rebuild for .cpp files end diff --git a/xmake/rules/c++/modules/modules_support/gcc/compiler_support.lua b/xmake/rules/c++/modules/modules_support/gcc/compiler_support.lua index c365a107a..2253c9866 100644 --- a/xmake/rules/c++/modules/modules_support/gcc/compiler_support.lua +++ b/xmake/rules/c++/modules/modules_support/gcc/compiler_support.lua @@ -65,6 +65,40 @@ function load(target) end end +-- strip flags that doesn't affect bmi generation +function strip_flags(target, flags) + -- speculative list as there is no resource that list flags that prevent reusability, this list will likely be improve over time + local strippable_flags = { + "-I", + "-isystem", + "-g", + "-O", + "-W", + "-w", + "-cxx-isystem", + "-Q", + } + if not target:policy("build.c++.modules.tryreuse.discriminate_on_defines") then + table.join2(strippable_flags, {"-D", "-U"}) + end + local output = {} + local last_flag_I = false + for _, flag in ipairs(flags) do + local strip = false + for _, _flag in ipairs(strippable_flags) do + if flag:startswith(_flag) or last_flag_I then + last_flag_I = _flag == "-I" + strip = true + break + end + end + if not strip then + table.insert(output, flag) + end + end + return output +end + -- provide toolchain include directories for stl headerunit when p1689 is not supported function toolchain_includedirs(target) local includedirs = _g.includedirs diff --git a/xmake/rules/c++/modules/modules_support/msvc/builder.lua b/xmake/rules/c++/modules/modules_support/msvc/builder.lua index 3fc86b369..1259019ba 100644 --- a/xmake/rules/c++/modules/modules_support/msvc/builder.lua +++ b/xmake/rules/c++/modules/modules_support/msvc/builder.lua @@ -47,6 +47,56 @@ function _make_modulebuildflags(target, provide, bmifile, opt) end return flags end +function _compile_one_step(target, bmifile, sourcefile, objectfile, provide, opt) + local ifcoutputflag = compiler_support.get_ifcoutputflag(target) + local interfaceflag = compiler_support.get_interfaceflag(target) + local internalpartitionflag = compiler_support.get_internalpartitionflag(target) + -- get flags + local flags = {"-TP"} + if provide then + table.join2(flags, ifcoutputflag, path(bmifile), provide.interface and interfaceflag or internalpartitionflag) + end + if opt and opt.batchcmds then + _batchcmds_compile(opt.batchcmds, target, flags, sourcefile, objectfile) + else + _compile(target, flags, sourcefile, objectfile) + end +end + +function _compile_bmi_step(target, bmifile, sourcefile, objectfile, provide, opt) + local ifcoutputflag = compiler_support.get_ifcoutputflag(target) + local interfaceflag = compiler_support.get_interfaceflag(target) + local ifconlyflag = compiler_support.get_ifconlyflag(target) + + if not ifconlyflag then + _compile_one_step(target, bmifile, sourcefile, objectfile, provide, opt) + else + local flags = {"-TP", ifcoutputflag, path(bmifile), interfaceflag, ifconlyflag} + if opt and opt.batchcmds then + _batchcmds_compile(opt.batchcmds, target, flags, sourcefile, bmifile) + else + _compile(target, flags, sourcefile, bmifile) + end + end +end + +function _compile_objectfile_step(target, bmifile, sourcefile, objectfile, provide, opt) + local ifconlyflag = compiler_support.get_ifconlyflag(target) + local interfaceflag = compiler_support.get_interfaceflag(target) + local internalpartitionflag = compiler_support.get_internalpartitionflag(target) + + local flags = {"-TP", (provide and provide.interface) and interfaceflag or internalpartitionflag} + if not ifconlyflag then + _compile_one_step(target, bmifile, sourcefile, objectfile, provide, opt) + else + if opt and opt.batchcmds then + _batchcmds_compile(opt.batchcmds, target, flags, sourcefile, objectfile) + else + _compile(target, flags, sourcefile, objectfile) + end + end +end + -- get flags for building a headerunit function _make_headerunitflags(target, headerunit, bmifile) @@ -215,16 +265,25 @@ function make_module_buildjobs(target, batchjobs, job_name, deps, opt) return { name = job_name, - deps = deps, + deps = table.join(target:name() .. "_populate_module_map", deps), sourcefile = opt.cppfile, job = batchjobs:newjob(name or opt.cppfile, function(index, total) local compinst = compiler.load("cxx", {target = target}) local compflags = compinst:compflags({sourcefile = opt.cppfile, target = target}) + local mapped_bmi + if provide and compiler_support.memcache():get2(target:name() .. name, "reuse") then + if not target:is_binary() then + return + else + mapped_bmi = get_from_target_mapper(target, name).bmi + end + end + local build if provide or compiler_support.has_module_extension(opt.cppfile) then - build = should_build(target, opt.cppfile, bmifile, {objectfile = opt.objectfile, requires = opt.module.requires}) + build = should_build(target, opt.cppfile, bmifile, {name = name, objectfile = opt.objectfile, requires = opt.module.requires}) -- needed to detect rebuild of dependencies if provide and build then @@ -239,7 +298,7 @@ function make_module_buildjobs(target, batchjobs, job_name, deps, opt) -- for cpp file we need to check after appendings the flags if build == nil then - build = should_build(target, opt.cppfile, bmifile, {objectfile = opt.objectfile, requires = opt.module.requires}) + build = should_build(target, opt.cppfile, bmifile, {name = name, objectfile = opt.objectfile, requires = opt.module.requires}) end local dependfile = target:dependfile(bmifile or opt.objectfile) @@ -250,8 +309,6 @@ function make_module_buildjobs(target, batchjobs, job_name, deps, opt) if build then -- compile if it's a named module if provide or compiler_support.has_module_extension(opt.cppfile) then - progress.show((index * 100) / total, "${color.build.target}<%s> ${clear}${color.build.object}compiling.module.$(mode) %s", target:name(), name or opt.cppfile) - if not dryrun then local objectdir = path.directory(opt.objectfile) if not os.isdir(objectdir) then @@ -262,10 +319,24 @@ function make_module_buildjobs(target, batchjobs, job_name, deps, opt) local fileconfig = target:fileconfig(opt.cppfile) local public = fileconfig and fileconfig.public local external = fileconfig and fileconfig.external - local build_objectfile = target:kind() == "binary" or (not public and not external) - local flags = _make_modulebuildflags(target, provide, bmifile, {build_objectfile = build_objectfile}) - - _compile(target, flags, opt.cppfile, opt.objectfile) + local bmifile = mapped_bmi or bmifile + if target:is_binary() then + if mapped_bmi then + progress.show((index * 100) / total, "${color.build.target}<%s> ${clear}${color.build.object}compiling.objectfile.$(mode) %s", target:name(), name or opt.cppfile) + _compile_objectfile_step(target, bmifile, opt.cppfile, opt.objectfile, provide) + else + progress.show((index * 100) / total, "${color.build.target}<%s> ${clear}${color.build.object}compiling.module.$(mode) %s", target:name(), name or opt.cppfile) + _compile_one_step(target, bmifile, opt.cppfile, opt.objectfile, provide) + end + else + if not public and not external then + progress.show((index * 100) / total, "${color.build.target}<%s> ${clear}${color.build.object}compiling.module.$(mode) %s", target:name(), name or opt.cppfile) + _compile_one_step(target, bmifile, opt.cppfile, opt.objectfile, provide) + else + progress.show((index * 100) / total, "${color.build.target}<%s> ${clear}${color.build.object}compiling.bmi.$(mode) %s", target:name(), name or opt.cppfile) + _compile_bmi_step(target, bmifile, opt.cppfile, opt.objectfile, provide) + end + end else os.tryrm(opt.objectfile) -- force rebuild for .cpp files end @@ -283,9 +354,18 @@ function make_module_buildcmds(target, batchcmds, opt) local name, provide, _ = compiler_support.get_provided_module(opt.module) local bmifile = provide and compiler_support.get_bmi_path(provide.bmi) + local mapped_bmi + if provide and compiler_support.memcache():get2(target:name() .. name, "reuse") then + if not target:is_binary() then + return + else + mapped_bmi = get_from_target_mapper(target, name).bmi + end + end + local build if provide or compiler_support.has_module_extension(opt.cppfile) then - build = should_build(target, opt.cppfile, bmifile, {objectfile = opt.objectfile, requires = opt.module.requires}) + build = should_build(target, opt.cppfile, bmifile, {name = name, objectfile = opt.objectfile, requires = opt.module.requires}) -- needed to detect rebuild of dependencies if provide and build then @@ -300,21 +380,35 @@ function make_module_buildcmds(target, batchcmds, opt) -- for cpp file we need to check after appendings the flags if build == nil then - build = should_build(target, opt.cppfile, bmifile, {objectfile = opt.objectfile, requires = opt.module.requires}) + build = should_build(target, opt.cppfile, bmifile, {name = name, objectfile = opt.objectfile, requires = opt.module.requires}) end if build then -- compile if it's a named module if provide or compiler_support.has_module_extension(opt.cppfile) then - batchcmds:show_progress(opt.progress, "${color.build.target}<%s> ${clear}${color.build.object}compiling.module.$(mode) %s", target:name(), name or opt.cppfile) batchcmds:mkdir(path.directory(opt.objectfile)) local fileconfig = target:fileconfig(opt.cppfile) local public = fileconfig and fileconfig.public local external = fileconfig and fileconfig.external - local build_objectfile = target:kind() == "binary" or (not public and not external) - local flags = _make_modulebuildflags(target, provide, bmifile, {build_objectfile = build_objectfile}) - _batchcmds_compile(batchcmds, target, flags, opt.cppfile, opt.objectfile) + local bmifile = mapped_bmi or bmifile + if target:is_binary() then + if mapped_bmi then + batchcmds:show_progress(opt.progress, "${color.build.target}<%s> ${clear}${color.build.object}compiling.objectfile.$(mode) %s", target:name(), name or opt.cppfile) + _compile_objectfile_step(target, bmifile, opt.cppfile, opt.objectfile, provide, {batchcmds = batchcmds}) + else + batchcmds:show_progress(opt.progress, "${color.build.target}<%s> ${clear}${color.build.object}compiling.module.$(mode) %s", target:name(), name or opt.cppfile) + _compile_one_step(target, bmifile, opt.cppfile, opt.objectfile, provide, {batchcmds = batchcmds}) + end + else + if not public and not external then + batchcmds:show_progress(opt.progress, "${color.build.target}<%s> ${clear}${color.build.object}compiling.module.$(mode) %s", target:name(), name or opt.cppfile) + _compile_one_step(target, bmifile, opt.cppfile, opt.objectfile, provide {batchcmds = batchcmds}) + else + batchcmds:show_progress(opt.progress, "${color.build.target}<%s> ${clear}${color.build.object}compiling.bmi.$(mode) %s", target:name(), name or opt.cppfile) + _compile_bmi_step(target, bmifile, opt.cppfile, provide, {batchcmds = batchcmds}) + end + end else batchcmds:rm(opt.objectfile) -- force rebuild for .cpp files end diff --git a/xmake/rules/c++/modules/modules_support/msvc/compiler_support.lua b/xmake/rules/c++/modules/modules_support/msvc/compiler_support.lua index e65effa11..69f677706 100644 --- a/xmake/rules/c++/modules/modules_support/msvc/compiler_support.lua +++ b/xmake/rules/c++/modules/modules_support/msvc/compiler_support.lua @@ -56,6 +56,64 @@ function load(target) end end +-- strip flags that doesn't affect bmi generation +function strip_flags(target, flags) + -- speculative list as there is no resource that list flags that prevent reusability, this list will likely be improve over time + -- @see https://learn.microsoft.com/en-us/cpp/build/reference/compiler-options-listed-alphabetically?view=msvc-170 + local strippable_flags = { + "I", + "TP", + "errorReport", + "W", + "w", + "sourceDependencies", + "scanDependencies", + "reference", + "PD", + "nologo", + "MP", + "internalPartition", + "interface", + "ifcOutput", + "help", + "headerUnit", + "headerName", + "Fp", + "Fo", + "Fm", + "Fe", + "Fd", + "FC", + "exportHeader", + "EP", + "E", + "doc", + "diagnostics", + "cgthreads", + "C", + "analyze", + "?", + } + if not target:policy("build.c++.modules.tryreuse.discriminate_on_defines") then + table.join2(strippable_flags, {"D", "U"}) + end + local output = {} + for _, flag in ipairs(flags) do + local strip = false + for _, _flag in ipairs(strippable_flags) do + if flag:startswith("cl::-" .. _flag) or flag:startswith("cl::/" .. _flag) or + flag:startswith("-" .. _flag) or flag:startswith("/" .. _flag) then + strip = true + break + end + end + if not strip then + table.insert(output, flag) + end + end + return output +end + -- provide toolchain include dir for stl headerunit when p1689 is not supported function toolchain_includedirs(target) for _, toolchain_inst in ipairs(target:toolchains()) do |
