summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorruki <[email protected]>2024-02-14 21:47:55 +0800
committerGitHub <[email protected]>2024-02-14 21:47:55 +0800
commitb1d3ab468f9fefaed3368d0a9bb90fb4f9c0807b (patch)
tree0814552f84537cf2e15e5d5b48ac324cd7dd5e85
parenta7396842b55fb3d9747b5da199d6d5ec1afb8aa9 (diff)
parent45b8fa3d0f456e820e6fdd38c7e000fa52064b4d (diff)
Merge pull request #4728 from Arthapz/reuse-modules
implement module reusage based on flag comparison
-rw-r--r--tests/projects/c++/modules/private_module/src/use.cpp8
-rw-r--r--tests/projects/c++/modules/private_module/src/use.mpp7
-rw-r--r--tests/projects/c++/modules/private_module/xmake.lua1
-rw-r--r--xmake/core/project/policy.lua4
-rw-r--r--xmake/rules/c++/modules/modules_support/builder.lua90
-rw-r--r--xmake/rules/c++/modules/modules_support/clang/builder.lua132
-rw-r--r--xmake/rules/c++/modules/modules_support/clang/compiler_support.lua35
-rw-r--r--xmake/rules/c++/modules/modules_support/compiler_support.lua5
-rw-r--r--xmake/rules/c++/modules/modules_support/dependency_scanner.lua2
-rw-r--r--xmake/rules/c++/modules/modules_support/gcc/builder.lua123
-rw-r--r--xmake/rules/c++/modules/modules_support/gcc/compiler_support.lua34
-rw-r--r--xmake/rules/c++/modules/modules_support/msvc/builder.lua124
-rw-r--r--xmake/rules/c++/modules/modules_support/msvc/compiler_support.lua58
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