summaryrefslogtreecommitdiff
path: root/xmake/rules/c++/modules/modules_support
diff options
context:
space:
mode:
authorruki <[email protected]>2022-08-16 12:33:47 +0800
committerGitHub <[email protected]>2022-08-16 12:33:47 +0800
commit81aca6fbe9149b377cdd88e8ee4b8f045d5a46da (patch)
tree0f138f960ab9fd95265ecbffca7c6ae27d65f4a3 /xmake/rules/c++/modules/modules_support
parentd46b16f8c40493bfc78764931bf7c3209442696b (diff)
parentd9f82e1bba29adc8b11dc1f7b3887717f03137d6 (diff)
Merge pull request #2641 from xmake-io/cxxmodules
Improve C++20 modules support
Diffstat (limited to 'xmake/rules/c++/modules/modules_support')
-rw-r--r--xmake/rules/c++/modules/modules_support/clang.lua603
-rw-r--r--xmake/rules/c++/modules/modules_support/common.lua555
-rw-r--r--xmake/rules/c++/modules/modules_support/gcc.lua457
-rw-r--r--xmake/rules/c++/modules/modules_support/msvc.lua688
-rw-r--r--xmake/rules/c++/modules/modules_support/stl_headers.lua153
5 files changed, 2456 insertions, 0 deletions
diff --git a/xmake/rules/c++/modules/modules_support/clang.lua b/xmake/rules/c++/modules/modules_support/clang.lua
new file mode 100644
index 000000000..fb3a2d1fa
--- /dev/null
+++ b/xmake/rules/c++/modules/modules_support/clang.lua
@@ -0,0 +1,603 @@
+--!A cross-platform build utility based on Lua
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-- Copyright (C) 2015-present, TBOOX Open Source Group.
+--
+-- @author ruki
+-- @file clang.lua
+--
+
+-- imports
+import("core.tool.compiler")
+import("core.project.project")
+import("core.project.depend")
+import("core.project.config")
+import("utils.progress")
+import("private.action.build.object", {alias = "objectbuilder"})
+import("common")
+import("stl_headers")
+
+-- add a module or an header unit into the mapper
+--
+-- e.g
+-- -fmodule-file=build/.gens/Foo/rules/modules/cache/foo.pcm
+-- -fmodule-file=build/.gens/Foo/rules/modules/cache/iostream.pcm
+-- -fmodule-file=build/.gens/Foo/rules/modules/cache/bar.hpp.pcm
+--
+function _add_module_to_mapper(target, name, bmifile, deps)
+ local modulemap = _get_modulemap_from_mapper(target)
+ if modulemap[name] then
+ return
+ end
+
+ local modulefileflag = get_modulefileflag(target)
+ local mapflag = format("%s%s", modulefileflag, bmifile)
+ modulemap[name] = {flag = mapflag, deps = deps}
+ common.localcache():set2(_mapper_cachekey(target), "modulemap", modulemap)
+end
+
+function _mapper_cachekey(target)
+ return target:name() .. "_modulemap"
+end
+
+-- flush modulemap to mapper file cache
+function _flush_mapper(target)
+ -- not using set2/get2 to flush only current target mapper
+ common.localcache():save(_mapper_cachekey(target))
+end
+
+-- get modulemap from mapper
+function _get_modulemap_from_mapper(target)
+ return common.localcache():get2(_mapper_cachekey(target), "modulemap") or {}
+end
+
+-- load module support for the current target
+function load(target)
+ -- get module and module cache flags
+ local modulesflag = get_modulesflag(target)
+ local builtinmodulemapflag = get_builtinmodulemapflag(target)
+ local implicitmodulesflag = get_implicitmodulesflag(target)
+ local noimplicitmodulemapsflag = get_noimplicitmodulemapsflag(target)
+
+ -- add module flags
+ target:add("cxxflags", modulesflag)
+
+ -- add the module cache directory
+ target:add("cxxflags", builtinmodulemapflag, {force = true})
+ target:add("cxxflags", implicitmodulesflag, {force = true})
+ target:add("cxxflags", noimplicitmodulemapsflag, {force = true})
+
+ target:data_set("cxx.modules.use_libc++", table.contains(target:get("cxxflags"), "-stdlib=libc++"))
+end
+
+-- get includedirs for stl headers
+--
+-- $ echo '#include <vector>' | clang -x c++ -E - | grep '/vector"'
+-- # 1 "/usr/include/c++/11/vector" 1 3
+-- # 58 "/usr/include/c++/11/vector" 3
+-- # 59 "/usr/include/c++/11/vector" 3
+--
+function _get_toolchain_includedirs_for_stlheaders(includedirs, clang)
+ local tmpfile = os.tmpfile() .. ".cc"
+ io.writefile(tmpfile, "#include <vector>")
+ local result = try {function () return os.iorunv(clang, {"-E", "-x", "c++", tmpfile}) end}
+ if result then
+ for _, line in ipairs(result:split("\n", {plain = true})) do
+ line = line:trim()
+ if line:startswith("#") and line:find("/vector\"", 1, true) then
+ local includedir = line:match("\"(.+)/vector\"")
+ if includedir and os.isdir(includedir) then
+ table.insert(includedirs, path.normalize(includedir))
+ break
+ end
+ end
+ end
+ end
+ os.tryrm(tmpfile)
+end
+
+-- provide toolchain include directories for stl headerunit when p1689 is not supported
+function toolchain_includedirs(target)
+ local includedirs = _g.includedirs
+ if includedirs == nil then
+ includedirs = {}
+ local clang, toolname = target:tool("cc")
+ assert(toolname == "clang")
+ _get_toolchain_includedirs_for_stlheaders(includedirs, clang)
+ local _, result = try {function () return os.iorunv(clang, {"-E", "-Wp,-v", "-xc", os.nuldev()}) end}
+ if result then
+ for _, line in ipairs(result:split("\n", {plain = true})) do
+ line = line:trim()
+ if os.isdir(line) then
+ table.insert(includedirs, path.normalize(line))
+ elseif line:startswith("End") then
+ break
+ end
+ end
+ end
+ _g.includedirs = includedirs
+ end
+ return includedirs
+end
+
+-- generate dependency files
+function generate_dependencies(target, sourcebatch, opt)
+ local changed = false
+ local cachedir = common.modules_cachedir(target)
+ for _, sourcefile in ipairs(sourcebatch.sourcefiles) do
+ local dependfile = target:dependfile(sourcefile)
+ depend.on_changed(function()
+ if opt.progress then
+ progress.show(opt.progress, "${color.build.object}generating.cxx.module.deps %s", sourcefile)
+ end
+
+ local outputdir = path.translate(path.join(cachedir, path.directory(path.relative(sourcefile, projectdir))))
+ if not os.isdir(outputdir) then
+ os.mkdir(outputdir)
+ end
+
+ -- no support of p1689 atm
+ local jsonfile = path.translate(path.join(outputdir, path.filename(sourcefile) .. ".json"))
+ common.fallback_generate_dependencies(target, jsonfile, sourcefile)
+ changed = true
+
+ local dependinfo = io.readfile(jsonfile)
+ return { moduleinfo = dependinfo }
+ end, {dependfile = dependfile, files = {sourcefile}})
+ end
+ return changed
+end
+
+-- generate target stl header units for batchjobs
+function generate_stl_headerunits_for_batchjobs(target, batchjobs, headerunits, opt)
+ local compinst = target:compiler("cxx")
+ local stlcachedir = common.stlmodules_cachedir(target)
+ local modulecachepathflag = get_modulecachepathflag(target)
+ assert(has_headerunitsupport(target), "compiler(clang): does not support c++ header units!")
+
+ -- flush job
+ local flushjob = batchjobs:addjob(target:name() .. "_stl_headerunits_flush_mapper", function(index, total)
+ _flush_mapper(target)
+ end, {rootjob = opt.rootjob})
+
+ -- build headerunits
+ for i, headerunit in ipairs(headerunits) do
+ local bmifile = path.join(stlcachedir, headerunit.name .. get_bmi_extension())
+ if not os.isfile(bmifile) then
+ batchjobs:addjob(headerunit.name, function (index, total)
+ depend.on_changed(function()
+ -- don't build same header unit at the same time
+ if not common.memcache():get2(headerunit.name, "building") then
+ common.memcache():set2(headerunit.name, "building", true)
+ progress.show((index * 100) / total, "${color.build.object}generating.cxx.headerunit.bmi %s", headerunit.name)
+ local args = {modulecachepathflag .. stlcachedir, "-c", "-o", bmifile, "-x", "c++-system-header", headerunit.name}
+ os.vrunv(compinst:program(), table.join(compinst:compflags({target = target}), args))
+ end
+
+ end, {dependfile = target:dependfile(bmifile), files = {headerunit.path}})
+ -- libc++ have a builtin module mapper
+ if not target:data_set("cxx.modules.use_libc++") then
+ _add_module_to_mapper(target, headerunit.name, bmifile)
+ end
+ end, {rootjob = flushjob})
+ end
+ end
+end
+
+-- generate target stl header units for batchcmds
+function generate_stl_headerunits_for_batchcmds(target, batchcmds, headerunits, opt)
+ local compinst = target:compiler("cxx")
+ local stlcachedir = common.stlmodules_cachedir(target)
+ local modulecachepathflag = get_modulecachepathflag(target)
+ assert(has_headerunitsupport(target), "compiler(clang): does not support c++ header units!")
+
+ -- build headerunits
+ local projectdir = os.projectdir()
+ local depmtime = 0
+ for i, headerunit in ipairs(headerunits) do
+ local bmifile = path.join(stlcachedir, headerunit.name .. get_bmi_extension())
+ -- don't build same header unit at the same time
+ if not common.memcache():get2(headerunit.name, "building") then
+ common.memcache():set2(headerunit.name, "building", true)
+ local args = {
+ path(stlcachedir, function (p) return modulecachepathflag .. p end),
+ "-c", "-o", path(bmifile), "-x", "c++-system-header", headerunit.name}
+ batchcmds:show_progress(opt.progress, "${color.build.object}generating.cxx.headerunit.bmi %s", headerunit.name)
+ batchcmds:vrunv(compinst:program(), table.join(compinst:compflags({target = target}), args))
+ end
+ -- libc++ have a builtin module mapper
+ if not target:data_set("cxx.modules.use_libc++") then
+ _add_module_to_mapper(target, headerunit.name, bmifile)
+ end
+ depmtime = math.max(depmtime, os.mtime(bmifile))
+ end
+ batchcmds:set_depmtime(depmtime)
+ _flush_mapper(target)
+end
+
+-- generate target user header units for batchjobs
+function generate_user_headerunits_for_batchjobs(target, batchjobs, headerunits, opt)
+ local compinst = target:compiler("cxx")
+ assert(has_headerunitsupport(target), "compiler(clang): does not support c++ header units!")
+
+ -- get cachedirs
+ local cachedir = common.modules_cachedir(target)
+ local modulecachepathflag = get_modulecachepathflag(target)
+
+ -- flush job
+ local flushjob = batchjobs:addjob(target:name() .. "_user_headerunits_flush_mapper", function(index, total)
+ _flush_mapper(target)
+ end, {rootjob = opt.rootjob})
+
+ -- build headerunits
+ local projectdir = os.projectdir()
+ for _, headerunit in ipairs(headerunits) do
+ local file = path.relative(headerunit.path, target:scriptdir())
+ local objectfile = target:objectfile(file)
+
+ local outputdir
+ if headerunit.type == ":quote" then
+ outputdir = path.join(cachedir, path.directory(path.relative(headerunit.path, projectdir)))
+ else
+ outputdir = path.join(cachedir, path.directory(headerunit.path))
+ end
+ local bmifilename = path.basename(objectfile) .. get_bmi_extension()
+ local bmifile = (outputdir and path.join(outputdir, bmifilename) or bmifilename)
+ batchjobs:addjob(headerunit.name, function (index, total)
+ depend.on_changed(function()
+ progress.show((index * 100) / total, "${color.build.object}generating.cxx.headerunit.bmi %s", headerunit.name)
+ local objectdir = path.directory(objectfile)
+ if not os.isdir(objectdir) then
+ os.mkdir(objectdir)
+ end
+ if not os.isdir(outputdir) then
+ os.mkdir(outputdir)
+ end
+
+ -- generate headerunit
+ local args = { modulecachepathflag .. cachedir, "-c", "-o", bmifile}
+ if headerunit.type == ":quote" then
+ table.join2(args, {"-I", path.directory(headerunit.path), "-x", "c++-user-header", headerunit.path})
+ elseif headerunit.type == ":angle" then
+ table.join2(args, {"-x", "c++-system-header", headerunit.name})
+ end
+ os.vrunv(compinst:program(), table.join(compinst:compflags({target = target}), args))
+
+ end, {dependfile = target:dependfile(bmifile), files = {headerunit.path}})
+ _add_module_to_mapper(target, headerunit.name, bmifile)
+ end, {rootjob = flushjob})
+ end
+end
+
+-- generate target user header units for batchcmds
+function generate_user_headerunits_for_batchcmds(target, batchcmds, headerunits, opt)
+ local compinst = target:compiler("cxx")
+ assert(has_headerunitsupport(target), "compiler(clang): does not support c++ header units!")
+
+ -- get cachedirs
+ local cachedir = common.modules_cachedir(target)
+ local modulecachepathflag = get_modulecachepathflag(target)
+
+ -- build headerunits
+ local projectdir = os.projectdir()
+ local depmtime = 0
+ for _, headerunit in ipairs(headerunits) do
+ local file = path.relative(headerunit.path, target:scriptdir())
+ local objectfile = target:objectfile(file)
+
+ local outputdir
+ if headerunit.type == ":quote" then
+ outputdir = path.join(cachedir, path.directory(path.relative(headerunit.path, projectdir)))
+ else
+ outputdir = path.join(cachedir, path.directory(headerunit.path))
+ end
+ batchcmds:mkdir(outputdir)
+
+ local bmifilename = path.basename(objectfile) .. get_bmi_extension()
+ local bmifile = (outputdir and path.join(outputdir, bmifilename) or bmifilename)
+ batchcmds:mkdir(path.directory(objectfile))
+
+ local args = {path(cachedir, function (p) return modulecachepathflag .. p end), "-c", "-o", path(bmifile)}
+ if headerunit.type == ":quote" then
+ table.join2(args, {"-I", path(headerunit.path):directory(), "-x", "c++-user-header", path(headerunit.path)})
+ elseif headerunit.type == ":angle" then
+ table.join2(args, {"-x", "c++-system-header", headerunit.name})
+ end
+
+ batchcmds:show_progress(opt.progress, "${color.build.object}generating.cxx.headerunit.bmi %s", headerunit.name)
+ batchcmds:vrunv(compinst:program(), table.join(compinst:compflags({target = target}), args))
+ batchcmds:add_depfiles(headerunit.path)
+
+ _add_module_to_mapper(target, headerunit.name, bmifile)
+
+ depmtime = math.max(depmtime, os.mtime(bmifile))
+ end
+ batchcmds:set_depmtime(depmtime)
+ _flush_mapper(target)
+end
+
+-- build module files for batchjobs
+function build_modules_for_batchjobs(target, batchjobs, objectfiles, modules, opt)
+ local compinst = target:compiler("cxx")
+ local cachedir = common.modules_cachedir(target)
+ local modulecachepathflag = get_modulecachepathflag(target)
+ local modulefileflag = get_modulefileflag(target)
+
+ -- flush job
+ local flushjob = batchjobs:addjob(target:name() .. "_stl_flush_mapper", function(index, total)
+ _flush_mapper(target)
+ end, {rootjob = opt.rootjob})
+
+ -- build modules
+ local common_args = {modulecachepathflag .. cachedir}
+ local modulesjobs = {}
+ for _, objectfile in ipairs(objectfiles) do
+ local module = modules[objectfile]
+ if module then
+ if module.provides then
+ -- assume there that provides is only one, until we encounter the case
+ local length = 0
+ local name, provide
+ for k, v in pairs(module.provides) do
+ length = length + 1
+ name = k
+ provide = v
+ if length > 1 then
+ raise("multiple provides are not supported now!")
+ end
+ end
+
+ local bmifile = provide.bmi
+ local moduleinfo = table.copy(provide)
+ moduleinfo.job = batchjobs:newjob(provide.sourcefile, function (index, total)
+ -- append module mapper flags first
+ -- @note we add it at the end to ensure that the full modulemap are already stored in the mapper
+ local requiresflags
+ if module.requires then
+ requiresflags = get_requiresflags(target, module.requires)
+ end
+
+ depend.on_changed(function()
+ progress.show((index * 100) / total, "${color.build.object}generating.cxx.module.bmi %s", name)
+ local bmidir = path.directory(bmifile)
+ if not os.isdir(bmidir) then
+ os.mkdir(bmidir)
+ end
+ local objectdir = path.directory(objectfile)
+ if not os.isdir(objectdir) then
+ os.mkdir(objectdir)
+ end
+ local args = { "-c", "-x", "c++-module", "--precompile", provide.sourcefile, "-o", bmifile}
+ os.vrunv(compinst:program(), table.join(compinst:compflags({target = target}), common_args, requiresflags or {}, args))
+ os.vrunv(compinst:program(), table.join(compinst:compflags({target = target}), common_args, requiresflags or {}, {bmifile}, {"-c", "-o", objectfile}))
+ end, {dependfile = target:dependfile(bmifile), files = {provide.sourcefile}})
+ _add_module_to_mapper(target, name, bmifile, requiresflags)
+ end)
+ if module.requires then
+ moduleinfo.deps = table.keys(module.requires)
+ end
+ moduleinfo.name = name
+ modulesjobs[name] = moduleinfo
+ target:add("objectfiles", objectfile)
+ else
+ if module.requires then
+ modulesjobs[module.cppfile] = {
+ name = module.cppfile,
+ deps = table.keys(module.requires),
+ sourcefile = module.cppfile,
+ job = batchjobs:newjob(module.cppfile, function(index, total)
+ -- append module mapper flags
+ -- @note we add it at the end to ensure that the full modulemap are already stored in the mapper
+ local requiresflags = get_requiresflags(target, module.requires)
+ if requiresflags then
+ target:fileconfig_add(module.cppfile, {force = {cxxflags = requiresflags}})
+ end
+ end)
+ }
+ end
+ end
+ end
+ end
+
+ -- build batchjobs for modules
+ common.build_batchjobs_for_modules(modulesjobs, batchjobs, flushjob)
+end
+
+-- build module files for batchcmds
+function build_modules_for_batchcmds(target, batchcmds, objectfiles, modules, opt)
+ local compinst = target:compiler("cxx")
+ local cachedir = common.modules_cachedir(target)
+ local modulecachepathflag = get_modulecachepathflag(target)
+ local modulefileflag = get_modulefileflag(target)
+
+ -- build modules
+ local depmtime = 0
+ local common_args = {path(cachedir, function (p) return modulecachepathflag .. p end)}
+ for _, objectfile in ipairs(objectfiles) do
+ local module = modules[objectfile]
+ if module then
+ if module.provides then
+ local name, provide
+ for k, v in pairs(module.provides) do
+ name = k
+ provide = v
+ break
+ end
+ local bmifile = provide.bmi
+ local args = {"-c", "-x", "c++-module", "--precompile", path(provide.sourcefile), "-o", path(bmifile)}
+ local requiresflags
+ if module.requires then
+ requiresflags = get_requiresflags(target, module.requires)
+ end
+ batchcmds:show_progress(opt.progress, "${color.build.object}generating.cxx.module.bmi %s", name)
+ batchcmds:mkdir(path.directory(objectfile))
+ batchcmds:vrunv(compinst:program(), table.join(compinst:compflags({target = target}), common_args, requiresflags or {}, args))
+ batchcmds:vrunv(compinst:program(), table.join(compinst:compflags({target = target}), common_args, requiresflags or {}, path(bmifile), {"-c", "-o", path(objectfile)}))
+ batchcmds:add_depfiles(provide.sourcefile)
+ _add_module_to_mapper(target, name, bmifile)
+ depmtime = math.max(depmtime, os.mtime(bmifile))
+ else
+ if module.requires then
+ local requiresflags = get_requiresflags(target, module.requires)
+ if requiresflags then
+ target:fileconfig_add(module.cppfile, {force = {cxxflags = requiresflags}})
+ end
+ end
+ end
+ end
+ end
+ batchcmds:set_depmtime(depmtime)
+ _flush_mapper(target)
+end
+
+function get_bmi_extension()
+ return ".pcm"
+end
+
+function get_modulesflag(target)
+ local modulesflag = _g.modulesflag
+ if modulesflag == nil then
+ local compinst = target:compiler("cxx")
+ if compinst:has_flags("-fmodules", "cxxflags", {flagskey = "clang_modules"}) then
+ modulesflag = "-fmodules"
+ end
+ if not modulesflag then
+ if compinst:has_flags("-fmodules-ts", "cxxflags", {flagskey = "clang_modules_ts"}) then
+ modulesflag = "-fmodules-ts"
+ end
+ end
+ assert(modulesflag, "compiler(clang): does not support c++ module!")
+ _g.modulesflag = modulesflag or false
+ end
+ return modulesflag or nil
+end
+
+function get_builtinmodulemapflag(target)
+ local builtinmodulemapflag = _g.builtinmodulemapflag
+ if builtinmodulemapflag == nil then
+ local compinst = target:compiler("cxx")
+ if compinst:has_flags("-fbuiltin-module-map", "cxxflags", {flagskey = "clang_builtin_module_map"}) then
+ builtinmodulemapflag = "-fbuiltin-module-map"
+ end
+ assert(builtinmodulemapflag, "compiler(clang): does not support c++ module!")
+ _g.builtinmodulemapflag = builtinmodulemapflag or false
+ end
+ return builtinmodulemapflag or nil
+end
+
+function get_implicitmodulesflag(target)
+ local implicitmodulesflag = _g.implicitmodulesflag
+ if implicitmodulesflag == nil then
+ local compinst = target:compiler("cxx")
+ if compinst:has_flags("-fimplicit-modules", "cxxflags", {flagskey = "clang_implicit_modules"}) then
+ implicitmodulesflag = "-fimplicit-modules"
+ end
+ assert(implicitmodulesflag, "compiler(clang): does not support c++ module!")
+ _g.implicitmodulesflag = implicitmodulesflag or false
+ end
+ return implicitmodulesflag or nil
+end
+
+function get_noimplicitmodulemapsflag(target)
+ local noimplicitmodulemapsflag = _g.noimplicitmodulemapsflag
+ if noimplicitmodulemapsflag == nil then
+ local compinst = target:compiler("cxx")
+ if compinst:has_flags("-fno-implicit-module-maps", "cxxflags", {flagskey = "clang_no_implicit_module_maps"}) then
+ noimplicitmodulemapsflag = "-fno-implicit-module-maps"
+ end
+ assert(noimplicitmodulemapsflag, "compiler(clang): does not support c++ module!")
+ _g.noimplicitmodulemapsflag = noimplicitmodulemapsflag or false
+ end
+ return noimplicitmodulemapsflag or nil
+end
+
+function get_prebuiltmodulepathflag(target)
+ local prebuiltmodulepathflag = _g.prebuiltmodulepathflag
+ if prebuiltmodulepathflag == nil then
+ local compinst = target:compiler("cxx")
+ if compinst:has_flags("-fprebuilt-module-path=" .. os.tmpdir(), "cxxflags", {flagskey = "clang_prebuild_module_path"}) then
+ prebuiltmodulepathflag = "-fprebuilt-module-path="
+ end
+ assert(prebuiltmodulepathflag, "compiler(clang): does not support c++ module!")
+ _g.prebuiltmodulepathflag = prebuiltmodulepathflag or false
+ end
+ return prebuiltmodulepathflag or nil
+end
+
+function get_modulecachepathflag(target)
+ local modulecachepathflag = _g.modulecachepathflag
+ if modulecachepathflag == nil then
+ local compinst = target:compiler("cxx")
+ if compinst:has_flags("-fmodules-cache-path=" .. os.tmpdir(), "cxxflags", {flagskey = "clang_modules_cache_path"}) then
+ modulecachepathflag = "-fmodules-cache-path="
+ end
+ assert(modulecachepathflag, "compiler(clang): does not support c++ module!")
+ _g.modulecachepathflag = modulecachepathflag or false
+ end
+ return modulecachepathflag or nil
+end
+
+function get_modulefileflag(target)
+ local modulefileflag = _g.modulefileflag
+ if modulefileflag == nil then
+ local compinst = target:compiler("cxx")
+ if compinst:has_flags("-fmodule-file=" .. os.tmpfile() .. get_bmi_extension(), "cxxflags", {flagskey = "clang_module_file"}) then
+ modulefileflag = "-fmodule-file="
+ end
+ assert(modulefileflag, "compiler(clang): does not support c++ module!")
+ _g.modulefileflag = modulefileflag or false
+ end
+ return modulefileflag or nil
+end
+
+function has_headerunitsupport(target)
+ local support_headerunits = _g.support_headerunits
+ if support_headerunits == nil then
+ local compinst = target:compiler("cxx")
+ if compinst:has_flags(get_modulesflag(target) .. " -std=c++20 -x c++-user-header", "cxxflags", {flagskey = "clang_user_header_unit_support", tryrun = true}) and
+ compinst:has_flags(get_modulesflag(target) .. " -std=c++20 -x c++-system-header", "cxxflags", {flagskey = "clang_system_header_unit_support", tryrun = true}) then
+ support_headerunits = true
+ end
+ _g.support_headerunits = support_headerunits or false
+ end
+ return support_headerunits or nil
+end
+
+function get_requiresflags(target, requires)
+ local flags = {}
+ local modulemap = _get_modulemap_from_mapper(target)
+ -- add deps required module flags
+ for name, _ in pairs(requires) do
+ for _, dep in ipairs(target:orderdeps()) do
+ local modulemap_ = _get_modulemap_from_mapper(dep)
+ if modulemap_[name] then
+ table.join2(flags, modulemap_[name].flag)
+ table.join2(flags, modulemap_[name].deps or {})
+ goto continue
+ end
+ end
+
+ -- append target required module mapper flags
+ if modulemap[name] then
+ table.join2(flags, modulemap[name].flag)
+ table.join2(flags, modulemap[name].deps or {})
+ goto continue
+ end
+
+ ::continue::
+ end
+ if #flags > 0 then
+ return table.unique(flags)
+ end
+end
diff --git a/xmake/rules/c++/modules/modules_support/common.lua b/xmake/rules/c++/modules/modules_support/common.lua
new file mode 100644
index 000000000..aa7c0fb59
--- /dev/null
+++ b/xmake/rules/c++/modules/modules_support/common.lua
@@ -0,0 +1,555 @@
+--!A cross-platform build utility based on Lua
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-- Copyright (C) 2015-present, TBOOX Open Source Group.
+--
+-- @author Arthapz, ruki
+-- @file common.lua
+--
+
+-- imports
+import("core.base.json")
+import("core.base.hashset")
+import("core.project.config")
+import("core.tool.compiler")
+import("core.cache.memcache", {alias = "_memcache"})
+import("core.cache.localcache", {alias = "_localcache"})
+import("core.project.project")
+import("lib.detect.find_file")
+import("stl_headers")
+
+-- get memcache
+function memcache()
+ return _memcache.cache("cxxmodules")
+end
+
+-- get localcache
+function localcache()
+ return _localcache.cache("cxxmodules")
+end
+
+-- get stl modules cache directory
+function stlmodules_cachedir(target)
+ local stlcachedir = path.join(config.buildir(), "stlmodules", "cache")
+ if not os.isdir(stlcachedir) then
+ os.mkdir(stlcachedir)
+ os.mkdir(path.join(stlcachedir, "experimental"))
+ end
+ return stlcachedir
+end
+
+-- get modules cache directory
+function modules_cachedir(target)
+ local cachedir = path.join(target:autogendir(), "rules", "modules", "cache")
+ if not os.isdir(cachedir) then
+ os.mkdir(cachedir)
+ end
+ return cachedir
+end
+
+-- get headerunits info
+function get_headerunits(target, sourcebatch, modules)
+ local headerunits
+ local stl_headerunits
+ for _, objectfile in ipairs(sourcebatch.objectfiles) do
+ local m = modules[objectfile]
+ if m then
+ for name, r in pairs(m.requires) do
+ if r.method ~= "by-name" then
+ local unittype = r.method == "include-angle" and ":angle" or ":quote"
+ if stl_headers.is_stl_header(name) then
+ stl_headerunits = stl_headerunits or {}
+ if not table.find_if(stl_headerunits, function(i, v) return v.name == name end) then
+ table.insert(stl_headerunits, {name = name, path = r.path, type = unittype})
+ end
+ else
+ headerunits = headerunits or {}
+ if not table.find_if(headerunits, function(i, v) return v.name == name end) then
+ table.insert(headerunits, {name = name, path = r.path, type = unittype})
+ end
+ end
+ end
+ end
+ end
+ end
+ return headerunits, stl_headerunits
+end
+
+-- patch sourcebatch
+function patch_sourcebatch(target, sourcebatch)
+ sourcebatch.sourcekind = "cxx"
+ sourcebatch.objectfiles = {}
+ sourcebatch.dependfiles = {}
+ for _, sourcefile in ipairs(sourcebatch.sourcefiles) do
+ local objectfile = target:objectfile(sourcefile)
+ local dependfile = target:dependfile(objectfile)
+ table.insert(sourcebatch.objectfiles, objectfile)
+ table.insert(sourcebatch.dependfiles, dependfile)
+ end
+end
+
+-- get modules support
+function modules_support(target)
+ local cachekey = tostring(target)
+ local module_builder = memcache():get2("modules_support", cachekey)
+ if module_builder == nil then
+ if target:has_tool("cxx", "clang", "clangxx") then
+ module_builder = import("clang", {anonymous = true})
+ elseif target:has_tool("cxx", "gcc", "gxx") then
+ module_builder = import("gcc", {anonymous = true})
+ elseif target:has_tool("cxx", "cl") then
+ module_builder = import("msvc", {anonymous = true})
+ else
+ local _, toolname = target:tool("cxx")
+ raise("compiler(%s): does not support c++ module!", toolname)
+ end
+ memcache():set2("modules_support", cachekey, module_builder)
+ end
+ return module_builder
+end
+
+-- get bmi extension
+function bmi_extension(target)
+ return modules_support(target).get_bmi_extension()
+end
+
+-- has module extension? e.g. *.mpp, ...
+function has_module_extension(sourcefile)
+ local modulexts = _g.modulexts
+ if modulexts == nil then
+ modulexts = hashset.of(".mpp", ".mxx", ".cppm", ".ixx")
+ _g.modulexts = modulexts
+ end
+ local extension = path.extension(sourcefile)
+ return modulexts:has(extension:lower())
+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
+ for _, dep in ipairs(target:orderdeps()) do
+ local sourcebatches = dep:sourcebatches()
+ if sourcebatches["c++.build.modules"] then
+ target_with_modules = true
+ break
+ end
+ end
+ end
+ return target_with_modules
+end
+
+-- load module infos
+function load_moduleinfos(target, sourcebatch)
+ local moduleinfos
+ for _, sourcefile in ipairs(sourcebatch.sourcefiles) do
+ local dependfile = target:dependfile(sourcefile)
+ if os.isfile(dependfile) then
+ local data = io.load(dependfile)
+ if data then
+ moduleinfos = moduleinfos or {}
+ local moduleinfo = json.decode(data.moduleinfo)
+ moduleinfo.sourcefile = sourcefile
+ if moduleinfo then
+ table.insert(moduleinfos, moduleinfo)
+ end
+ end
+ end
+ end
+ return moduleinfos
+end
+
+-- parse module dependency data
+--[[
+{
+ "build/.objs/stl_headerunit/linux/x86_64/release/src/hello.mpp.o" = {
+ requires = {
+ iostream = {
+ method = "include-angle",
+ unique = true,
+ path = "/usr/include/c++/11/iostream"
+ }
+ },
+ provides = {
+ hello = {
+ bmi = "build/.gens/stl_headerunit/linux/x86_64/release/rules/modules/cache/hello.gcm",
+ sourcefile = "src/hello.mpp"
+ }
+ }
+ },
+ "build/.objs/stl_headerunit/linux/x86_64/release/src/main.cpp.o" = {
+ requires = {
+ hello = {
+ method = "by-name",
+ unique = false,
+ path = "build/.gens/stl_headerunit/linux/x86_64/release/rules/modules/cache/hello.gcm"
+ }
+ }
+ }
+}]]
+function parse_dependency_data(target, moduleinfos)
+ local modules
+ local cachedir = modules_cachedir(target)
+ for _, moduleinfo in ipairs(moduleinfos) do
+ assert(moduleinfo.version <= 1)
+ for _, rule in ipairs(moduleinfo.rules) do
+ modules = modules or {}
+ local m = {}
+ if rule.provides then
+ for _, provide in ipairs(rule.provides) do
+ m.provides = m.provides or {}
+ assert(provide["logical-name"])
+ if provide["compiled-module-path"] then
+ if not path.is_absolute(provide["compiled-module-path"]) then
+ m.provides[provide["logical-name"]] = path.absolute(path.translate(provide["compiled-module-path"]))
+ else
+ m.provides[provide["logical-name"]] = path.translate(provide["compiled-module-path"])
+ end
+ else
+ -- assume path with name
+ local name = provide["logical-name"] .. bmi_extension(target)
+ name:replace(":", "-")
+ m.provides[provide["logical-name"]] = {
+ bmi = path.join(cachedir, name),
+ sourcefile = moduleinfo.sourcefile
+ }
+ end
+ end
+ else
+ m.cppfile = moduleinfo.sourcefile
+ end
+ assert(rule["primary-output"])
+ modules[path.translate(rule["primary-output"])] = m
+ end
+ end
+
+ for _, moduleinfo in ipairs(moduleinfos) do
+ for _, rule in ipairs(moduleinfo.rules) do
+ local m = modules[path.translate(rule["primary-output"])]
+ for _, r in ipairs(rule.requires) do
+ m.requires = m.requires or {}
+ local p = r["source-path"]
+ if not p then
+ for _, dependency in pairs(modules) do
+ if dependency.provides and dependency.provides[r["logical-name"]] then
+ p = dependency.provides[r["logical-name"]].bmi
+ break
+ end
+ end
+ end
+ m.requires[r["logical-name"]] = {
+ method = r["lookup-method"] or "by-name",
+ path = p and path.translate(p) or nil,
+ unique = r["unique-on-source-path"] or false
+ }
+ end
+ end
+ end
+ return modules
+end
+
+function _topological_sort_visit(node, nodes, modules, output)
+ if node.marked then
+ return
+ end
+ assert(not node.tempmarked)
+ node.tempmarked = true
+ local m1 = modules[node.objectfile]
+ for _, n in ipairs(nodes) do
+ if not n.tempmarked then
+ local m2 = modules[n.objectfile]
+ if m2 then
+ for name, provide in pairs(m1.provides) do
+ if m2.requires and m2.requires[name] then
+ _topological_sort_visit(n, nodes, modules, output)
+ end
+ end
+ end
+ end
+ end
+ node.tempmarked = false
+ node.marked = true
+ table.insert(output, 1, node.objectfile)
+end
+
+function _topological_sort_has_node_without_mark(nodes)
+ for _, node in ipairs(nodes) do
+ if not node.marked then
+ return true
+ end
+ end
+ return false
+end
+
+function _topological_sort_get_first_unmarked_node(nodes)
+ for _, node in ipairs(nodes) do
+ if not node.marked and not node.tempmarked then
+ return node
+ end
+ end
+end
+
+-- topological sort
+function sort_modules_by_dependencies(objectfiles, modules)
+ local output = {}
+ local nodes = {}
+ for _, objectfile in ipairs(objectfiles) do
+ local m = modules[objectfile]
+ if m then
+ table.insert(nodes, {marked = false, tempmarked = false, objectfile = objectfile})
+ end
+ end
+ while _topological_sort_has_node_without_mark(nodes) do
+ local node = _topological_sort_get_first_unmarked_node(nodes)
+ _topological_sort_visit(node, nodes, modules, output)
+ end
+ return output
+end
+
+function find_quote_header_file(target, sourcefile, file)
+ local p = path.join(path.directory(path.absolute(sourcefile, project.directory())), file)
+ assert(os.isfile(p))
+ return p
+end
+
+function find_angle_header_file(target, file)
+ local headerpaths = modules_support(target).toolchain_includedirs(target)
+ for _, dep in ipairs(target:orderdeps()) do
+ local includedirs = dep:get("sysincludedirs") or dep:get("includedirs")
+ if includedirs then
+ table.join2(headerpaths, includedirs)
+ end
+ end
+ for _, pkg in pairs(target:pkgs()) do
+ local includedirs = pkg:get("sysincludedirs") or pkg:get("includedirs")
+ if includedirs then
+ table.join2(headerpaths, includedirs)
+ end
+ end
+ table.join2(headerpaths, target:get("includedirs"))
+ local p = find_file(file, headerpaths)
+ assert(p, "find <%s> not found!", file)
+ return p
+end
+
+-- https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p1689r5.html
+--[[
+{
+ "version": 1,
+ "revision": 0,
+ "rules": [
+ {
+ "primary-output": "use-header.mpp.o",
+ "requires": [
+ {
+ "logical-name": "<header.hpp>",
+ "source-path": "/path/to/found/header.hpp",
+ "unique-on-source-path": true,
+ "lookup-method": "include-angle"
+ }
+ ]
+ },
+ {
+ "primary-output": "header.hpp.bmi",
+ "provides": [
+ {
+ "logical-name": "header.hpp",
+ "source-path": "/path/to/found/header.hpp",
+ "unique-on-source-path": true,
+ }
+ ]
+ }
+ ]
+}]]
+function fallback_generate_dependencies(target, jsonfile, sourcefile)
+ local output = {version = 0, revision = 0, rules = {}}
+ local rule = {outputs = {jsonfile}}
+ rule["primary-output"] = target:objectfile(sourcefile)
+
+ local module_name
+ local module_deps = {}
+ local sourcecode = io.readfile(sourcefile)
+ sourcecode = sourcecode:gsub("//.-\n", "\n")
+ sourcecode = sourcecode:gsub("/%*.-%*/", "")
+ for _, line in ipairs(sourcecode:split("\n", {plain = true})) do
+ if not module_name then
+ module_name = line:match("export%s+module%s+(.+)%s*;")
+ end
+ local module_depname = line:match("import%s+(.+)%s*;")
+ -- we need parse module interface dep in cxx/impl_unit.cpp, e.g. hello.mpp and hello_impl.cpp
+ -- @see https://github.com/xmake-io/xmake/pull/2664#issuecomment-1213167314
+ if not module_depname and not has_module_extension(sourcefile) then
+ module_depname = line:match("module%s+(.+)%s*;")
+ end
+ if module_depname then
+ local module_dep = {}
+ -- partition? import :xxx;
+ if module_depname:startswith(":") then
+ module_depname = module_name .. module_depname
+ elseif module_depname:startswith("\"") then
+ module_depname = module_depname:sub(2, -2)
+ module_dep["lookup-method"] = "include-quote"
+ module_dep["unique-on-source-path"] = true
+ module_dep["source-path"] = find_quote_header_file(target, sourcefile, module_depname)
+ elseif module_depname:startswith("<") then
+ module_depname = module_depname:sub(2, -2)
+ module_dep["lookup-method"] = "include-angle"
+ module_dep["unique-on-source-path"] = true
+ module_dep["source-path"] = find_angle_header_file(target, module_depname)
+ end
+ module_dep["logical-name"] = module_depname
+ table.insert(module_deps, module_dep)
+ end
+ end
+
+ if module_name then
+ table.insert(rule.outputs, module_name .. bmi_extension(target))
+
+ local provide = {}
+ provide["logical-name"] = module_name
+ provide["source-path"] = path.absolute(sourcefile, project.directory())
+
+ rule.provides = {}
+ table.insert(rule.provides, provide)
+ end
+
+ rule.requires = module_deps
+ table.insert(output.rules, rule)
+ local jsondata = json.encode(output)
+ io.writefile(jsonfile, jsondata)
+end
+
+-- get module dependencies
+function get_module_dependencies(target, sourcebatch, opt)
+ local cachekey = target:name() .. "/" .. sourcebatch.rulename
+ local modules = memcache():get2("modules", cachekey)
+ if modules == nil then
+ modules = localcache():get2("modules", cachekey)
+ opt.progress = opt.progress or 0
+ local changed = modules_support(target).generate_dependencies(target, sourcebatch, opt)
+ if changed or modules == nil then
+ local moduleinfos = load_moduleinfos(target, sourcebatch)
+ modules = parse_dependency_data(target, moduleinfos)
+ localcache():set2("modules", cachekey, modules)
+ localcache():save()
+ end
+ memcache():set2("modules", cachekey, modules)
+ end
+ return modules
+end
+
+-- generate headerunits for batchjobs
+function generate_headerunits_for_batchjobs(target, batchjobs, sourcebatch, modules, opt)
+
+ -- get headerunits info
+ local headerunits, stl_headerunits = get_headerunits(target, sourcebatch, modules)
+
+ -- generate headerunits
+ -- build stl header units as other headerunits may need them
+ local headerunits_flags
+ if stl_headerunits then
+ headerunits_flags = headerunits_flags or {}
+ table.join2(headerunits_flags, modules_support(target).generate_stl_headerunits_for_batchjobs(target, batchjobs, stl_headerunits, opt))
+ end
+ if headerunits then
+ headerunits_flags = headerunits_flags or {}
+ table.join2(headerunits_flags, modules_support(target).generate_user_headerunits_for_batchjobs(target, batchjobs, headerunits, opt))
+ end
+ return headerunits_flags
+end
+
+-- generate headerunits for batchcmds
+function generate_headerunits_for_batchcmds(target, batchcmds, sourcebatch, modules, opt)
+
+ -- get headerunits info
+ local user_headerunits, stl_headerunits = get_headerunits(target, sourcebatch, modules)
+
+ -- generate headerunits
+ -- build stl header units as other headerunits may need them
+ if stl_headerunits or user_headerunits then
+ local headerunits_flags = localcache():get("headerunits_flags")
+ if stl_headerunits then
+ modules_support(target).generate_stl_headerunits_for_batchcmds(target, batchcmds, stl_headerunits, opt)
+ end
+ if user_headerunits then
+ modules_support(target).generate_user_headerunits_for_batchcmds(target, batchcmds, user_headerunits, opt)
+ end
+ end
+end
+
+-- build batch jobs for module dependencies
+function _build_batchjobs_for_moduledeps(modules, batchjobs, rootjob, jobrefs, moduleinfo)
+ local targetjob_ref = jobrefs[moduleinfo.name]
+ if targetjob_ref then
+ batchjobs:add(targetjob_ref, rootjob)
+ else
+ local modulejob = batchjobs:add(moduleinfo.job, rootjob)
+ if modulejob then
+ jobrefs[moduleinfo.name] = modulejob
+ for _, depname in ipairs(moduleinfo.deps) do
+ local dep = modules[depname]
+ if dep then -- maybe nil, e.g. `import <string>;`
+ _build_batchjobs_for_moduledeps(modules, batchjobs, modulejob, jobrefs, dep)
+ end
+ end
+ end
+ end
+end
+
+-- build batchjobs for modules
+function build_batchjobs_for_modules(modules, batchjobs, rootjob)
+ local depset = hashset.new()
+ for _, moduleinfo in pairs(modules) do
+ assert(moduleinfo.job)
+ for _, depname in ipairs(moduleinfo.deps) do
+ depset:insert(depname)
+ end
+ end
+ local modules_root = {}
+ for _, moduleinfo in pairs(modules) do
+ if not depset:has(moduleinfo.name) then
+ table.insert(modules_root, moduleinfo)
+ end
+ end
+ local jobrefs = {}
+ for _, moduleinfo in pairs(modules_root) do
+ _build_batchjobs_for_moduledeps(modules, batchjobs, rootjob, jobrefs, moduleinfo)
+ end
+end
+
+-- build modules for batchjobs
+function build_modules_for_batchjobs(target, batchjobs, sourcebatch, modules, opt)
+ local objectfiles = sort_modules_by_dependencies(sourcebatch.objectfiles, modules)
+ modules_support(target).build_modules_for_batchjobs(target, batchjobs, objectfiles, modules, opt)
+end
+
+-- build modules for batchcmds
+function build_modules_for_batchcmds(target, batchcmds, sourcebatch, modules, opt)
+ local objectfiles = sort_modules_by_dependencies(sourcebatch.objectfiles, modules)
+ modules_support(target).build_modules_for_batchcmds(target, batchcmds, objectfiles, modules, opt)
+end
+
+-- append headerunits objectfiles to link
+function append_headerunits_objectfiles(target)
+ local cachekey = target:name() .. "headerunit_objectfiles"
+ local cache = localcache():get(cachekey) or {}
+ if target:is_binary() then
+ target:add("ldflags", cache, {force = true})
+ elseif target:is_static() then
+ target:add("arflags", cache, {force = true})
+ elseif target:is_shared() then
+ target:add("shflags", cache, {force = true})
+ end
+end
diff --git a/xmake/rules/c++/modules/modules_support/gcc.lua b/xmake/rules/c++/modules/modules_support/gcc.lua
new file mode 100644
index 000000000..1405d1cfa
--- /dev/null
+++ b/xmake/rules/c++/modules/modules_support/gcc.lua
@@ -0,0 +1,457 @@
+--!A cross-platform build utility based on Lua
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-- Copyright (C) 2015-present, TBOOX Open Source Group.
+--
+-- @author ruki
+-- @file gcc.lua
+--
+
+-- imports
+import("core.tool.compiler")
+import("core.project.project")
+import("core.project.depend")
+import("core.project.config")
+import("utils.progress")
+import("private.action.build.object", {alias = "objectbuilder"})
+import("common")
+
+-- get and create the path of module mapper
+function _get_module_mapper()
+ local mapper_file = path.join(config.buildir(), "mapper.txt")
+ if not os.isfile(mapper_file) then
+ io.writefile(mapper_file, "")
+ end
+ return mapper_file
+end
+
+-- add a module or header unit into the mapper
+--
+-- e.g
+-- /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 _add_module_to_mapper(file, module, bmi)
+ for line in io.lines(file) do
+ if line:startswith(module .. " ") then
+ return false
+ end
+ end
+ local f = io.open(file, "a")
+ f:print("%s %s", module, bmi)
+ f:close()
+ return true
+end
+
+-- load module support for the current target
+function load(target)
+ local modulesflag = get_modulesflag(target)
+ local modulemapperflag = get_modulemapperflag(target)
+ target:add("cxxflags", modulesflag)
+ if os.isfile(_get_module_mapper()) then
+ os.rm(_get_module_mapper())
+ end
+ target:add("cxxflags", modulemapperflag .. _get_module_mapper(), {force = true, expand = false})
+end
+
+-- get includedirs for stl headers
+--
+-- $ echo '#include <vector>' | gcc -x c++ -E - | grep '/vector"'
+-- # 1 "/usr/include/c++/11/vector" 1 3
+-- # 58 "/usr/include/c++/11/vector" 3
+-- # 59 "/usr/include/c++/11/vector" 3
+--
+function _get_toolchain_includedirs_for_stlheaders(includedirs, gcc)
+ local tmpfile = os.tmpfile() .. ".cc"
+ io.writefile(tmpfile, "#include <vector>")
+ local result = try {function () return os.iorunv(gcc, {"-E", "-x", "c++", tmpfile}) end}
+ if result then
+ for _, line in ipairs(result:split("\n", {plain = true})) do
+ line = line:trim()
+ if line:startswith("#") and line:find("/vector\"", 1, true) then
+ local includedir = line:match("\"(.+)/vector\"")
+ if includedir and os.isdir(includedir) then
+ table.insert(includedirs, path.normalize(includedir))
+ break
+ end
+ end
+ end
+ end
+ os.tryrm(tmpfile)
+end
+
+-- provide toolchain include directories for stl headerunit when p1689 is not supported
+function toolchain_includedirs(target)
+ local includedirs = _g.includedirs
+ if includedirs == nil then
+ includedirs = {}
+ local gcc, toolname = target:tool("cc")
+ assert(toolname == "gcc")
+ _get_toolchain_includedirs_for_stlheaders(includedirs, gcc)
+ local _, result = try {function () return os.iorunv(gcc, {"-E", "-Wp,-v", "-xc", os.nuldev()}) end}
+ if result then
+ for _, line in ipairs(result:split("\n", {plain = true})) do
+ line = line:trim()
+ if os.isdir(line) then
+ table.insert(includedirs, path.normalize(line))
+ elseif line:startswith("End") then
+ break
+ end
+ end
+ end
+ _g.includedirs = includedirs
+ end
+ return includedirs
+end
+
+-- generate dependency files
+function generate_dependencies(target, sourcebatch, opt)
+ local cachedir = common.modules_cachedir(target)
+ local compinst = target:compiler("cxx")
+ local common_args = {"-E", "-x", "c++"}
+ local trtbdflag = get_trtbdflag(target)
+ local depfileflag = get_depfileflag(target)
+ local depoutputflag = get_depoutputflag(target)
+ local changed = false
+ for _, sourcefile in ipairs(sourcebatch.sourcefiles) do
+ local dependfile = target:dependfile(sourcefile)
+ depend.on_changed(function()
+ if opt.progress then
+ progress.show(opt.progress, "${color.build.object}generating.cxx.module.deps %s", sourcefile)
+ end
+
+ local outputdir = path.translate(path.join(cachedir, path.directory(path.relative(sourcefile, projectdir))))
+ if not os.isdir(outputdir) then
+ os.mkdir(outputdir)
+ end
+
+ local jsonfile = path.translate(path.join(outputdir, path.filename(sourcefile) .. ".json"))
+ if trtbdflag and depfileflag and depoutputflag then
+ local ifile = path.translate(path.join(outputdir, path.filename(sourcefile) .. ".i"))
+ local dfile = path.translate(path.join(outputdir, path.filename(sourcefile) .. ".d"))
+ local args = {sourcefile, "-MD", "-MT", jsonfile, "-MF", dfile, depfileflag .. jsonfile, trtbdflag, depoutputfile .. target:objectfile(sourcefile), "-o", ifile}
+ os.vrunv(compinst:program(), table.join(compinst:compflags({target = target}), common_args, args), {envs = vcvars})
+ else
+ common.fallback_generate_dependencies(target, jsonfile, sourcefile)
+ end
+ changed = true
+
+ local dependinfo = io.readfile(jsonfile)
+ return { moduleinfo = dependinfo }
+ end, {dependfile = dependfile, files = {sourcefile}})
+ end
+ return changed
+end
+
+-- generate target stl header units for batchjobs
+function generate_stl_headerunits_for_batchjobs(target, batchjobs, headerunits, opt)
+ local compinst = target:compiler("cxx")
+ local mapper_file = _get_module_mapper()
+ local stlcachedir = common.stlmodules_cachedir(target)
+
+ -- build headerunits
+ local projectdir = os.projectdir()
+ for _, headerunit in ipairs(headerunits) do
+ local bmifile = path.join(stlcachedir, headerunit.name .. get_bmi_extension())
+ if not os.isfile(bmifile) then
+ batchjobs:addjob(headerunit.name, function (index, total)
+ depend.on_changed(function()
+ progress.show((index * 100) / total, "${color.build.object}generating.cxx.headerunit.bmi %s", headerunit.name)
+ local args = {"-c", "-x", "c++-system-header", headerunit.name}
+ os.vrunv(compinst:program(), table.join(compinst:compflags({target = target}), args))
+ end, {dependfile = target:dependfile(bmifile), files = {headerunit.path}})
+ end, {rootjob = opt.rootjob})
+ end
+ _add_module_to_mapper(mapper_file, headerunit.path, path.absolute(bmifile, projectdir))
+ end
+end
+
+-- generate target stl header units for batchcmds
+function generate_stl_headerunits_for_batchcmds(target, batchcmds, headerunits, opt)
+ local compinst = target:compiler("cxx")
+ local mapper_file = _get_module_mapper()
+ local stlcachedir = common.stlmodules_cachedir(target)
+
+ -- build headerunits
+ local projectdir = os.projectdir()
+ local depmtime = 0
+ for _, headerunit in ipairs(headerunits) do
+ local bmifile = path.join(stlcachedir, headerunit.name .. get_bmi_extension())
+ if not os.isfile(bmifile) then
+ local args = {"-c", "-x", "c++-system-header", headerunit.name}
+ batchcmds:show_progress(opt.progress, "${color.build.object}generating.cxx.headerunit.bmi %s", headerunit.name)
+ batchcmds:vrunv(compinst:program(), table.join(compinst:compflags({target = target}), args))
+ end
+ batchcmds:add_depfiles(headerunit.path)
+ _add_module_to_mapper(mapper_file, headerunit.path, path.absolute(bmifile, projectdir))
+ depmtime = math.max(depmtime, os.mtime(bmifile))
+ end
+ batchcmds:set_depmtime(depmtime)
+end
+
+-- generate target user header units for batchjobs
+function generate_user_headerunits_for_batchjobs(target, batchjobs, headerunits, opt)
+ local compinst = target:compiler("cxx")
+ local mapper_file = _get_module_mapper()
+ local cachedir = common.modules_cachedir(target)
+
+ -- build headerunits
+ local projectdir = os.projectdir()
+ for _, headerunit in ipairs(headerunits) do
+ local file = path.relative(headerunit.path, projectdir)
+ local objectfile = target:objectfile(file)
+ local outputdir
+ local headerunit_path
+ if headerunit.type == ":quote" then
+ outputdir = path.join(cachedir, path.directory(path.relative(headerunit.path, projectdir)))
+ else
+ outputdir = path.join(cachedir, path.directory(headerunit.path))
+ end
+ local bmifilename = path.basename(objectfile) .. get_bmi_extension()
+ local bmifile = (outputdir and path.join(outputdir, bmifilename) or bmifilename)
+ if headerunit.type == ":quote" then
+ headerunit_path = path.join(".", path.relative(headerunit.path, projectdir))
+ elseif headerunit.type == ":angle" then
+ -- if path is relative then its a subtarget path
+ headerunit_path = path.is_absolute(headerunit.path) and headerunit.path or path.join(".", headerunit.path)
+ end
+ batchjobs:addjob(headerunit.name, function (index, total)
+ depend.on_changed(function()
+ progress.show((index * 100) / total, "${color.build.object}generating.cxx.headerunit.bmi %s", headerunit.name)
+ local objectdir = path.directory(objectfile)
+ if not os.isdir(objectdir) then
+ os.mkdir(objectdir)
+ end
+ if not os.isdir(outputdir) then
+ os.mkdir(outputdir)
+ end
+
+ -- generate headerunit
+ local args = { "-c" }
+ if headerunit.type == ":quote" then
+ table.join2(args, { "-I", path.directory(path.relative(headerunit.path, projectdir)), "-x", "c++-user-header", headerunit.name })
+ elseif headerunit.type == ":angle" then
+ table.join2(args, { "-x", "c++-system-header", headerunit.name })
+ end
+ os.vrunv(compinst:program(), table.join(compinst:compflags({target = target}), args))
+
+ end, {dependfile = target:dependfile(bmifile), files = {headerunit.path}})
+ end, {rootjob = opt.rootjob})
+ _add_module_to_mapper(mapper_file, headerunit_path, path.absolute(bmifile, projectdir))
+ end
+end
+
+-- generate target user header units for batchcmds
+function generate_user_headerunits_for_batchcmds(target, batchcmds, headerunits, opt)
+ local compinst = target:compiler("cxx")
+ local mapper_file = _get_module_mapper()
+ local cachedir = common.modules_cachedir(target)
+
+ -- build headerunits
+ local projectdir = os.projectdir()
+ local depmtime = 0
+ for _, headerunit in ipairs(headerunits) do
+ local file = path.relative(headerunit.path, projectdir)
+ local objectfile = target:objectfile(file)
+ local outputdir
+ if headerunit.type == ":quote" then
+ outputdir = path.join(cachedir, path.directory(path.relative(headerunit.path, projectdir)))
+ else
+ outputdir = path.join(cachedir, path.directory(headerunit.path))
+ end
+ batchcmds:mkdir(outputdir)
+
+ local bmifilename = path.basename(objectfile) .. get_bmi_extension()
+ local bmifile = (outputdir and path.join(outputdir, bmifilename) or bmifilename)
+ batchcmds:mkdir(path.directory(objectfile))
+
+ local args = {"-c"}
+ local headerunit_path
+ if headerunit.type == ":quote" then
+ table.join2(args, {"-I", path(path.relative(headerunit.path, projectdir)):directory(), "-x", "c++-user-header", headerunit.name})
+ headerunit_path = path.join(".", path.relative(headerunit.path, projectdir))
+ elseif headerunit.type == ":angle" then
+ table.join2(args, {"-x", "c++-system-header", headerunit.name})
+ -- if path is relative then its a subtarget path
+ headerunit_path = path.is_absolute(headerunit.path) and headerunit.path or path.join(".", headerunit.path)
+ end
+
+ batchcmds:show_progress(opt.progress, "${color.build.object}generating.cxx.headerunit.bmi %s", headerunit.name)
+ batchcmds:vrunv(compinst:program(), table.join(compinst:compflags({target = target}), args))
+ batchcmds:add_depfiles(headerunit.path)
+
+ _add_module_to_mapper(mapper_file, headerunit_path, path.absolute(bmifile, projectdir))
+ depmtime = math.max(depmtime, os.mtime(bmifile))
+ end
+ batchcmds:set_depmtime(depmtime)
+end
+
+-- build module files for batchjobs
+function build_modules_for_batchjobs(target, batchjobs, objectfiles, modules, opt)
+ local compinst = target:compiler("cxx")
+ local mapper_file = _get_module_mapper()
+ local common_args = {"-x", "c++"}
+ local cachedir = common.modules_cachedir(target)
+
+ -- build modules
+ local projectdir = os.projectdir()
+ local provided_modules = {}
+ for _, objectfile in ipairs(objectfiles) do
+ local m = modules[objectfile]
+ if m and m.provides then
+ -- assume there that provides is only one, until we encounter the case
+ local length = 0
+ local name, provide
+ for k, v in pairs(m.provides) do
+ length = length + 1
+ name = k
+ provide = v
+ if length > 1 then
+ raise("multiple provides are not supported now!")
+ end
+ end
+
+ local bmifile = provide.bmi
+ local moduleinfo = table.copy(provide)
+ moduleinfo.job = batchjobs:newjob(provide.sourcefile, function (index, total)
+ depend.on_changed(function()
+ progress.show((index * 100) / total, "${color.build.object}generating.cxx.module.bmi %s", name)
+ local objectdir = path.directory(objectfile)
+ if not os.isdir(objectdir) then
+ os.mkdir(objectdir)
+ end
+ local args = {"-o", objectfile, "-c", provide.sourcefile}
+ os.vrunv(compinst:program(), table.join(compinst:compflags({target = target}), common_args, args))
+ end, {dependfile = target:dependfile(bmifile), files = {provide.sourcefile}})
+ end)
+ if m.requires then
+ moduleinfo.deps = table.keys(m.requires)
+ end
+ moduleinfo.name = name
+ provided_modules[name] = moduleinfo
+ _add_module_to_mapper(mapper_file, name, path.absolute(bmifile, projectdir))
+ target:add("objectfiles", objectfile)
+ end
+ end
+
+ -- build batchjobs for modules
+ common.build_batchjobs_for_modules(provided_modules, batchjobs, opt.rootjob)
+end
+
+-- build module files for batchcmds
+function build_modules_for_batchcmds(target, batchcmds, objectfiles, modules, opt)
+ local compinst = target:compiler("cxx")
+ local mapper_file = _get_module_mapper()
+ local common_args = {"-x", "c++"}
+ local cachedir = common.modules_cachedir(target)
+
+ -- build modules
+ local projectdir = os.projectdir()
+ local depmtime = 0
+ for _, objectfile in ipairs(objectfiles) do
+ local m = modules[objectfile]
+ if m and m.provides then
+ -- assume there that provides is only one, until we encounter the case
+ local length = 0
+ local name, provide
+ for k, v in pairs(m.provides) do
+ length = length + 1
+ name = k
+ provide = v
+ if length > 1 then
+ raise("multiple provides are not supported now!")
+ end
+ end
+
+ local bmifile = provide.bmi
+ local args = {"-o", path(objectfile), "-c", path(provide.sourcefile)}
+ batchcmds:show_progress(opt.progress, "${color.build.object}generating.cxx.module.bmi %s", name)
+ batchcmds:mkdir(path.directory(objectfile))
+ batchcmds:vrunv(compinst:program(), table.join(compinst:compflags({target = target}), common_args, args))
+ batchcmds:add_depfiles(provide.sourcefile)
+
+ _add_module_to_mapper(mapper_file, name, path.absolute(bmifile, projectdir))
+
+ target:add("objectfiles", objectfile)
+ depmtime = math.max(depmtime, os.mtime(bmifile))
+ end
+ end
+ batchcmds:set_depmtime(depmtime)
+end
+
+function get_bmi_extension()
+ return ".gcm"
+end
+
+function get_modulesflag(target)
+ local modulesflag = _g.modulesflag
+ if modulesflag == nil then
+ local compinst = target:compiler("cxx")
+ if compinst:has_flags("-fmodules-ts", "cxxflags", {flagskey = "gcc_modules_ts"}) then
+ modulesflag = "-fmodules-ts"
+ end
+ assert(modulesflag, "compiler(gcc): does not support c++ module!")
+ _g.modulesflag = modulesflag or false
+ end
+ return modulesflag or nil
+end
+
+function get_modulemapperflag(target)
+ local modulemapperflag = _g.modulemapperflag
+ if modulemapperflag == nil then
+ local compinst = target:compiler("cxx")
+ if compinst:has_flags("-fmodule-mapper=" .. os.tmpfile(), "cxxflags", {flagskey = "gcc_module_mapper"}) then
+ modulemapperflag = "-fmodule-mapper="
+ end
+ assert(modulemapperflag, "compiler(gcc): does not support c++ module!")
+ _g.modulemapperflag = modulemapperflag or false
+ end
+ return modulemapperflag or nil
+end
+
+function get_trtbdflag(target)
+ local trtbdflag = _g.trtbdflag
+ if trtbdflag == nil then
+ local compinst = target:compiler("cxx")
+ if compinst:has_flags("-fdep-format=trtbd", "cxxflags", {flagskey = "gcc_dep_format"}) then
+ trtbdflag = "-fdep-format=trtbd"
+ end
+ _g.trtbdflag = trtbdflag or false
+ end
+ return trtbdflag or nil
+end
+
+function get_depfileflag(target)
+ local depfileflag = _g.depfileflag
+ if depfileflag == nil then
+ local compinst = target:compiler("cxx")
+ if compinst:has_flags("-fdep-file=" .. os.tmpfile(), "cxxflags", {flagskey = "gcc_dep_file"}) then
+ depfileflag = "-fdep-file="
+ end
+ _g.depfileflag = depfileflag or false
+ end
+ return depfileflag or nil
+end
+
+function get_depoutputflag(target)
+ local depoutputflag = _g.depoutputflag
+ if depoutputflag == nil then
+ local compinst = target:compiler("cxx")
+ if compinst:has_flags("-fdep-output=" .. os.tmpfile() .. ".o", "cxxflags", {flagskey = "gcc_dep_output"}) then
+ depoutputflag = "-fdep-output="
+ end
+ _g.depoutputflag = depoutputflag or false
+ end
+ return depoutputflag or nil
+end
diff --git a/xmake/rules/c++/modules/modules_support/msvc.lua b/xmake/rules/c++/modules/modules_support/msvc.lua
new file mode 100644
index 000000000..5214f12df
--- /dev/null
+++ b/xmake/rules/c++/modules/modules_support/msvc.lua
@@ -0,0 +1,688 @@
+--!A cross-platform build utility based on Lua
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-- Copyright (C) 2015-present, TBOOX Open Source Group.
+--
+-- @author ruki, Arthapz
+-- @file msvc.lua
+--
+
+-- imports
+import("core.tool.compiler")
+import("core.project.project")
+import("core.project.depend")
+import("core.project.config")
+import("core.base.hashset")
+import("utils.progress")
+import("private.action.build.object", {alias = "objectbuilder"})
+import("common")
+
+-- add a module or header unit into the mapper
+--
+-- e.g
+-- /headerUnit:angle Foo=build/.gens/Foo/rules/modules/cache/Foo.ifc
+-- /headerUnit:angle glm/mat4x4.hpp=Users\arthu\AppData\Local\.xmake\packages\g\glm\0.9.9+8\91454f3ee0be416cb9c7452970a2300f\include\glm\mat4x4.hpp.ifc
+--
+function _add_module_to_mapper(target, argument, name, bmifile, deps)
+ local modulemap = _get_modulemap_from_mapper(target)
+ if modulemap[name] then
+ return
+ end
+ local mapflag = {argument, name .. "=" .. bmifile}
+ modulemap[name] = {flag = mapflag, deps = deps}
+ common.localcache():set2(_mapper_cachekey(target), "modulemap", modulemap)
+end
+
+function _mapper_cachekey(target)
+ return target:name() .. "_modulemap"
+end
+
+-- flush mapper file cache
+function _flush_mapper(target)
+ -- not using set2/get2 to flush only current target mapper
+ common.localcache():save(_mapper_cachekey(target))
+end
+
+-- get modulemap from mapper
+function _get_modulemap_from_mapper(target)
+ return common.localcache():get2(_mapper_cachekey(target), "modulemap") or {}
+end
+
+-- add an objectfile to the linker args
+--
+-- e.g
+-- foo.obj
+--
+function _add_objectfile_to_link_arguments(target, objectfile)
+ local cachekey = target:name() .. "headerunit_objectfiles"
+ local cache = common.localcache():get(cachekey) or {}
+ if table.contains(cache, objectfile) then
+ return
+ end
+ table.insert(cache, objectfile)
+ common.localcache():set(cachekey, cache)
+ common.localcache():save(cachekey)
+end
+
+-- load module support for the current target
+function load(target)
+ -- get flags
+ local modulesflag = get_modulesflag(target)
+
+ -- add modules flags
+ target:add("cxxflags", modulesflag)
+
+ -- add stdifcdir in case of if the user ask for it
+ if target:values("msvc.modules.stdifcdir") then
+ local stdifcdirflag = get_stdifcdirflag(target)
+ for _, toolchain_inst in ipairs(target:toolchains()) do
+ if toolchain_inst:name() == "msvc" then
+ local vcvars = toolchain_inst:config("vcvars")
+ if vcvars.VCInstallDir and vcvars.VCToolsVersion then
+ local stdifcdir = path.join(vcvars.VCInstallDir, "Tools", "MSVC", vcvars.VCToolsVersion, "ifc", target:is_arch("x64") and "x64" or "x86")
+ if os.isdir(stdifcdir) then
+ target:add("cxxflags", {stdifcdirflag, winos.short_path(stdifcdir)}, {force = true, expand = false})
+ end
+ end
+ break
+ end
+ end
+ end
+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
+ if toolchain_inst:name() == "msvc" then
+ local vcvars = toolchain_inst:config("vcvars")
+ if vcvars.VCInstallDir and vcvars.VCToolsVersion then
+ return { path.join(vcvars.VCInstallDir, "Tools", "MSVC", vcvars.VCToolsVersion, "include") }
+ end
+ break
+ end
+ end
+ raise("msvc toolchain includedirs not found!")
+end
+
+-- generate dependency files
+function generate_dependencies(target, sourcebatch, opt)
+ local compinst = target:compiler("cxx")
+ local toolchain = target:toolchain("msvc")
+ local vcvars = toolchain:config("vcvars")
+ local scandependenciesflag = get_scandependenciesflag(target)
+ local common_args = {"-TP", scandependenciesflag}
+ local cachedir = common.modules_cachedir(target)
+ local changed = false
+ for _, sourcefile in ipairs(sourcebatch.sourcefiles) do
+ local dependfile = target:dependfile(sourcefile)
+ depend.on_changed(function ()
+ if opt.progress then
+ progress.show(opt.progress, "${color.build.object}generating.cxx.module.deps %s", sourcefile)
+ end
+ local outputdir = path.join(cachedir, path.directory(path.relative(sourcefile, projectdir)))
+ if not os.isdir(outputdir) then
+ os.mkdir(outputdir)
+ end
+
+ local jsonfile = path.join(outputdir, path.filename(sourcefile) .. ".json")
+ if scandependenciesflag then
+ local args = {jsonfile, sourcefile, "-Fo" .. target:objectfile(sourcefile)}
+ os.vrunv(compinst:program(), table.join(compinst:compflags({target = target}), common_args, args), {envs = vcvars})
+ else
+ common.fallback_generate_dependencies(target, jsonfile, sourcefile)
+ end
+ changed = true
+
+ local dependinfo = io.readfile(jsonfile)
+ return { moduleinfo = dependinfo }
+ end, {dependfile = dependfile, files = {sourcefile}})
+ end
+ return changed
+end
+
+-- generate target stl header units for batchjobs
+function generate_stl_headerunits_for_batchjobs(target, batchjobs, headerunits, opt)
+ local compinst = target:compiler("cxx")
+ local toolchain = target:toolchain("msvc")
+ local vcvars = toolchain:config("vcvars")
+ local stlcachedir = common.stlmodules_cachedir(target)
+
+ -- get flags
+ local exportheaderflag = get_exportheaderflag(target)
+ local headerunitflag = get_headerunitflag(target)
+ local headernameflag = get_headernameflag(target)
+ local ifcoutputflag = get_ifcoutputflag(target)
+ assert(headerunitflag and headernameflag and exportheaderflag, "compiler(msvc): does not support c++ header units!")
+
+ -- flush job
+ local flushjob = batchjobs:addjob(target:name() .. "_stl_headerunits_flush_mapper", function(index, total)
+ _flush_mapper(target)
+ end, {rootjob = opt.rootjob})
+
+ -- build headerunits
+ local common_args = {"-TP", exportheaderflag, "-c"}
+ for _, headerunit in ipairs(headerunits) do
+ local bmifile = path.join(stlcachedir, headerunit.name .. get_bmi_extension())
+ local objectfile = bmifile .. ".obj"
+ if not os.isfile(bmifile) or not os.isfile(objectfile) then
+ batchjobs:addjob(headerunit.name, function(index, total)
+ depend.on_changed(function()
+ -- don't build same header unit at the same time
+ if not common.memcache():get2(headerunit.name, "building") then
+ common.memcache():set2(headerunit.name, "building", true)
+ progress.show((index * 100) / total, "${color.build.object}generating.cxx.headerunit.bmi %s", headerunit.name)
+ local args = {headernameflag .. ":angle", headerunit.name, ifcoutputflag, headerunit.name:startswith("experimental/") and path.join(stlcachedir, "experimental") or stlcachedir, "-Fo" .. objectfile}
+ os.vrunv(compinst:program(), table.join(compinst:compflags({target = target}), common_args, args), {envs = vcvars})
+ end
+
+ end, {dependfile = target:dependfile(bmifile), files = {headerunit.path}})
+ _add_module_to_mapper(target, headerunitflag .. ":angle", headerunit.name, bmifile)
+ if os.isfile(objectfile) then
+ _add_objectfile_to_link_arguments(target, objectfile)
+ end
+ end, {rootjob = flushjob})
+ end
+ end
+end
+
+-- generate target stl header units for batchcmds
+function generate_stl_headerunits_for_batchcmds(target, batchcmds, headerunits, opt)
+ local compinst = target:compiler("cxx")
+ local toolchain = target:toolchain("msvc")
+ local vcvars = toolchain:config("vcvars")
+ local stlcachedir = common.stlmodules_cachedir(target)
+
+ -- get flags
+ local exportheaderflag = get_exportheaderflag(target)
+ local headerunitflag = get_headerunitflag(target)
+ local headernameflag = get_headernameflag(target)
+ local ifcoutputflag = get_ifcoutputflag(target)
+ assert(headerunitflag and headernameflag and exportheaderflag, "compiler(msvc): does not support c++ header units!")
+
+ -- build headerunits
+ local common_args = {"-TP", exportheaderflag, "-c"}
+ local depmtime = 0
+ for _, headerunit in ipairs(headerunits) do
+ local bmifile = path.join(stlcachedir, headerunit.name .. get_bmi_extension())
+ local objectfile = bmifile .. ".obj"
+ -- don't build same header unit at the same time
+ if not common.memcache():get2(headerunit.name, "building") then
+ common.memcache():set2(headerunit.name, "building", true)
+ local args = {
+ headernameflag .. ":angle",
+ headerunit.name,
+ ifcoutputflag,
+ path(headerunit.name:startswith("experimental/") and path.join(stlcachedir, "experimental") or stlcachedir),
+ path(objectfile, function (p) return "-Fo" .. p end)}
+ batchcmds:show_progress(opt.progress, "${color.build.object}generating.cxx.headerunit.bmi %s", headerunit.name)
+ batchcmds:vrunv(compinst:program(), table.join(compinst:compflags({target = target}), common_args, args), {envs = vcvars})
+ batchcmds:add_depfiles(headerunit.path)
+ end
+ _add_module_to_mapper(target, headerunitflag .. ":angle", headerunit.name, bmifile)
+ if os.isfile(objectfile) then
+ _add_objectfile_to_link_arguments(target, objectfile)
+ end
+ depmtime = math.max(depmtime, os.mtime(bmifile))
+ end
+ batchcmds:set_depmtime(depmtime)
+ _flush_mapper(target)
+end
+
+-- generate target user header units for batchcmds
+function generate_user_headerunits_for_batchjobs(target, batchjobs, headerunits, opt)
+ local compinst = target:compiler("cxx")
+ local toolchain = target:toolchain("msvc")
+ local vcvars = toolchain:config("vcvars")
+ local cachedir = common.modules_cachedir(target)
+
+ -- get flags
+ local exportheaderflag = get_exportheaderflag(target)
+ local headerunitflag = get_headerunitflag(target)
+ local headernameflag = get_headernameflag(target)
+ local ifcoutputflag = get_ifcoutputflag(target)
+ assert(headerunitflag and headernameflag and exportheaderflag, "compiler(msvc): does not support c++ header units!")
+
+ -- flush job
+ local flushjob = batchjobs:addjob(target:name() .. "_user_headerunits_flush_mapper", function(index, total)
+ _flush_mapper(target)
+ end, {rootjob = opt.rootjob})
+
+ -- build headerunits
+ local common_args = {"-TP", exportheaderflag, "-c"}
+ local projectdir = os.projectdir()
+ for _, headerunit in ipairs(headerunits) do
+ local file = path.relative(headerunit.path, target:scriptdir())
+ local objectfile = target:objectfile(file)
+ local outputdir
+ if headerunit.type == ":quote" then
+ outputdir = path.join(cachedir, path.directory(path.relative(headerunit.path, projectdir)))
+ else
+ -- if path is relative then its a subtarget path
+ outputdir = path.join(cachedir, path.is_absolute(headerunit.path) and path.directory(headerunit.path):sub(3) or headerunit.path)
+ end
+ local bmifilename = path.basename(objectfile) .. get_bmi_extension()
+ local bmifile = path.join(outputdir, bmifilename)
+ batchjobs:addjob(headerunit.name, function (index, total)
+ depend.on_changed(function()
+ if not common.memcache():get2(headerunit.name, "building") then
+ common.memcache():set2(headerunit.name, "building", true)
+ progress.show((index * 100) / total, "${color.build.object}generating.cxx.headerunit.bmi %s", headerunit.name)
+ local objectdir = path.directory(objectfile)
+ if not os.isdir(objectdir) then
+ os.mkdir(objectdir)
+ end
+ if not os.isdir(outputdir) then
+ os.mkdir(outputdir)
+ end
+
+ -- generate headerunit
+ local args = {headernameflag .. headerunit.type, headerunit.path, ifcoutputflag, outputdir, "/Fo" .. objectfile}
+ os.vrunv(compinst:program(), table.join(compinst:compflags({target = target}), common_args, args), {envs = vcvars})
+ end
+ _add_module_to_mapper(target, headerunitflag .. headerunit.type, headerunit.name, bmifile)
+ if os.isfile(objectfile) then
+ _add_objectfile_to_link_arguments(target, objectfile)
+ end
+ end, {dependfile = target:dependfile(bmifile), files = {headerunit.path}})
+ end, {rootjob = flushjob})
+ end
+end
+
+-- generate target user header units for batchcmds
+function generate_user_headerunits_for_batchcmds(target, batchcmds, headerunits, opt)
+ local compinst = target:compiler("cxx")
+ local toolchain = target:toolchain("msvc")
+ local vcvars = toolchain:config("vcvars")
+ local cachedir = common.modules_cachedir(target)
+
+ -- get flags
+ local exportheaderflag = get_exportheaderflag(target)
+ local headerunitflag = get_headerunitflag(target)
+ local headernameflag = get_headernameflag(target)
+ local ifcoutputflag = get_ifcoutputflag(target)
+ assert(headerunitflag and headernameflag and exportheaderflag, "compiler(msvc): does not support c++ header units!")
+
+ -- build headerunits
+ local common_args = {"-TP", exportheaderflag, "-c"}
+ local projectdir = os.projectdir()
+ local depmtime = 0
+ for _, headerunit in ipairs(headerunits) do
+ local file = path.relative(headerunit.path, target:scriptdir())
+ local objectfile = target:objectfile(file)
+ local outputdir
+ if headerunit.type == ":quote" then
+ outputdir = path.join(cachedir, path.directory(path.relative(headerunit.path, projectdir)))
+ else
+ -- if path is relative then its a subtarget path
+ outputdir = path.join(cachedir, path.is_absolute(headerunit.path) and path.directory(headerunit.path):sub(3) or headerunit.path)
+ end
+ batchcmds:mkdir(outputdir)
+
+ local bmifilename = path.basename(objectfile) .. get_bmi_extension()
+ local bmifile = path.join(outputdir, bmifilename)
+ batchcmds:mkdir(path.directory(objectfile))
+
+ local args = {headernameflag .. headerunit.type, headerunit.path, ifcoutputflag, outputdir, "/Fo" .. objectfile}
+
+ batchcmds:show_progress(opt.progress, "${color.build.object}generating.cxx.headerunit.bmi %s", headerunit.name)
+ batchcmds:vrunv(compinst:program(), table.join(compinst:compflags({target = target}), common_args, args), {envs = vcvars})
+ batchcmds:add_depfiles(headerunit.path)
+
+ _add_module_to_mapper(target, headerunitflag .. headerunit.type, headerunit.name, bmifile)
+ _add_objectfile_to_link_arguments(target, objectfile)
+
+ depmtime = math.max(depmtime, os.mtime(bmifile))
+ end
+ batchcmds:set_depmtime(depmtime)
+ _flush_mapper(target)
+end
+
+-- build module files for batchjobs
+function build_modules_for_batchjobs(target, batchjobs, objectfiles, modules, opt)
+ local compinst = target:compiler("cxx")
+ local toolchain = target:toolchain("msvc")
+ local vcvars = toolchain:config("vcvars")
+
+ -- get flags
+ local ifcoutputflag = get_ifcoutputflag(target)
+ local interfaceflag = get_interfaceflag(target)
+ local referenceflag = get_referenceflag(target)
+
+ -- flush job
+ local flushjob = batchjobs:addjob(target:name() .. "_modules", function(index, total)
+ _flush_mapper(target)
+ end, {rootjob = opt.rootjob})
+
+ local common_args = {"-TP"}
+ local modulesjobs = {}
+ for _, objectfile in ipairs(objectfiles) do
+ local module = modules[objectfile]
+ if module then
+ if module.provides then
+ -- assume there that provides is only one, until we encounter the case
+ local length = 0
+ local name, provide
+ for k, v in pairs(module.provides) do
+ length = length + 1
+ name = k
+ provide = v
+ if length > 1 then
+ raise("multiple provides are not supported now!")
+ end
+ end
+
+ local bmifile = provide.bmi
+ local moduleinfo = table.copy(provide)
+ moduleinfo.job = batchjobs:newjob(provide.sourcefile, function (index, total)
+ -- append module mapper flags first
+ -- @note we add it at the end to ensure that the full modulemap are already stored in the mapper
+ local requiresflags
+ if module.requires then
+ requiresflags = get_requiresflags(target, module.requires, {expand = true})
+ end
+ depend.on_changed(function()
+ progress.show((index * 100) / total, "${color.build.object}generating.cxx.module.bmi %s", name)
+ local objectdir = path.directory(objectfile)
+ if not os.isdir(objectdir) then
+ os.mkdir(objectdir)
+ end
+ local args = {"-c", "-Fo" .. objectfile, interfaceflag, ifcoutputflag, bmifile, provide.sourcefile}
+ os.vrunv(compinst:program(), table.join(compinst:compflags({target = target}), common_args, requiresflags or {}, args), {envs = vcvars})
+ end, {dependfile = target:dependfile(bmifile), files = {provide.sourcefile}})
+ _add_module_to_mapper(target, referenceflag, name, bmifile, requiresflags)
+ end)
+ if module.requires then
+ moduleinfo.deps = table.keys(module.requires)
+ end
+ moduleinfo.name = name
+ modulesjobs[name] = moduleinfo
+ target:add("objectfiles", objectfile)
+ else
+ if module.requires then
+ modulesjobs[module.cppfile] = {
+ name = module.cppfile,
+ deps = table.keys(module.requires),
+ sourcefile = module.cppfile,
+ job = batchjobs:newjob(module.cppfile, function(index, total)
+ function contains(t, v)
+ for _, flag in ipairs(t) do
+ if table.contains(flag, v) then
+ return true
+ end
+ end
+ return false
+ end
+ -- append module mapper flags
+ -- @note we add it at the end to ensure that the full modulemap are already stored in the mapper
+ local requiresflags = get_requiresflags(target, module.requires)
+ if requiresflags then
+ target:fileconfig_add(module.cppfile, {force = {cxxflags = requiresflags}})
+ end
+ end)
+ }
+ end
+ end
+ end
+ end
+
+ -- build batchjobs for modules
+ common.build_batchjobs_for_modules(modulesjobs, batchjobs, flushjob)
+end
+
+-- build module files for batchcmds
+function build_modules_for_batchcmds(target, batchcmds, objectfiles, modules, opt)
+ local compinst = target:compiler("cxx")
+ local toolchain = target:toolchain("msvc")
+ local vcvars = toolchain:config("vcvars")
+ local cachedir = common.modules_cachedir(target)
+
+ -- get flags
+ local ifcoutputflag = get_ifcoutputflag(target)
+ local interfaceflag = get_interfaceflag(target)
+ local referenceflag = get_referenceflag(target)
+
+ -- build modules
+ local common_args = {"-TP"}
+ local depmtime = 0
+ for _, objectfile in ipairs(objectfiles) do
+ local module = modules[objectfile]
+ if module then
+ if module.provides then
+ local name, provide
+ for k, v in pairs(module.provides) do
+ name = k
+ provide = v
+ break
+ end
+
+ -- append required modulemap flags to module
+ local requiresflags
+ if module.requires then
+ requiresflags = get_requiresflags(target, module.requires, {expand = true})
+ end
+
+ local bmifile = provide.bmi
+ local args = {"-c",
+ path(objectfile, function (p) return "-Fo" .. p end),
+ interfaceflag,
+ ifcoutputflag,
+ path(bmifile),
+ path(provide.sourcefile)}
+ batchcmds:show_progress(opt.progress, "${color.build.object}generating.cxx.module.bmi %s", name)
+ batchcmds:mkdir(path.directory(objectfile))
+ batchcmds:vrunv(compinst:program(), table.join(compinst:compflags({target = target}), common_args, requiresflags or {}, args), {envs = vcvars})
+ batchcmds:add_depfiles(provide.sourcefile)
+ _add_module_to_mapper(target, referenceflag, name, bmifile, requiresflags)
+ depmtime = math.max(depmtime, os.mtime(bmifile))
+ else
+ if module.requires then
+ local requiresflags = get_requiresflags(target, module.requires)
+ if requiresflags then
+ target:fileconfig_add(module.cppfile, {force = {cxxflags = requiresflags}})
+ end
+ end
+ end
+ end
+ end
+
+ batchcmds:set_depmtime(depmtime)
+ _flush_mapper(target)
+end
+
+function get_bmi_extension()
+ return ".ifc"
+end
+
+function get_modulesflag(target)
+ local modulesflag = _g.modulesflag
+ if modulesflag == nil then
+ local compinst = target:compiler("cxx")
+ if compinst:has_flags("-experimental:module", "cxxflags", {flagskey = "cl_experimental_module"}) then
+ modulesflag = "-experimental:module"
+ end
+ assert(modulesflag, "compiler(msvc): does not support c++ module!")
+ _g.modulesflag = modulesflag or false
+ end
+ return modulesflag or nil
+end
+
+function get_ifcoutputflag(target)
+ local ifcoutputflag = _g.ifcoutputflag
+ if ifcoutputflag == nil then
+ local compinst = target:compiler("cxx")
+ if compinst:has_flags("-ifcOutput", "cxxflags", {flagskey = "cl_ifc_output"}) then
+ ifcoutputflag = "-ifcOutput"
+ end
+ assert(ifcoutputflag, "compiler(msvc): does not support c++ module!")
+ _g.ifcoutputflag = ifcoutputflag or false
+ end
+ return ifcoutputflag or nil
+end
+
+function get_ifcsearchdirflag(target)
+ local ifcsearchdirflag = _g.ifcsearchdirflag
+ if ifcsearchdirflag == nil then
+ local compinst = target:compiler("cxx")
+ if compinst:has_flags("-ifcSearchDir", "cxxflags", {flagskey = "cl_ifc_search_dir"}) then
+ ifcsearchdirflag = "-ifcSearchDir"
+ end
+ assert(ifcsearchdirflag, "compiler(msvc): does not support c++ module!")
+ _g.ifcsearchdirflag = ifcsearchdirflag or false
+ end
+ return ifcsearchdirflag or nil
+end
+
+function get_interfaceflag(target)
+ local interfaceflag = _g.interfaceflag
+ if interfaceflag == nil then
+ local compinst = target:compiler("cxx")
+ if compinst:has_flags("-interface", "cxxflags", {flagskey = "cl_interface"}) then
+ interfaceflag = "-interface"
+ end
+ assert(interfaceflag, "compiler(msvc): does not support c++ module!")
+ _g.interfaceflag = interfaceflag or false
+ end
+ return interfaceflag
+end
+
+function get_referenceflag(target)
+ local referenceflag = _g.referenceflag
+ if referenceflag == nil then
+ local compinst = target:compiler("cxx")
+ if compinst:has_flags("-reference", "cxxflags", {flagskey = "cl_reference"}) then
+ referenceflag = "-reference"
+ end
+ assert(referenceflag, "compiler(msvc): does not support c++ module!")
+ _g.referenceflag = referenceflag or false
+ end
+ return referenceflag or nil
+end
+
+function get_headernameflag(target)
+ local headernameflag = _g.headernameflag
+ if headernameflag == nil then
+ local compinst = target:compiler("cxx")
+ if compinst:has_flags("-headerName:quote", "cxxflags", {flagskey = "cl_header_name_quote"}) and
+ compinst:has_flags("-headerName:angle", "cxxflags", {flagskey = "cl_header_name_angle"}) then
+ headernameflag = "-headerName"
+ end
+ _g.headernameflag = headernameflag or false
+ end
+ return headernameflag or nil
+end
+
+function get_headerunitflag(target)
+ local headerunitflag = _g.headerunitflag
+ if headerunitflag == nil then
+ local compinst = target:compiler("cxx")
+ if compinst:has_flags("-headerUnit:quote", "cxxflags", {flagskey = "cl_header_unit_quote"}) and
+ compinst:has_flags("-headerUnit:angle", "cxxflags", {flagskey = "cl_header_unit_angle"}) then
+ headerunitflag = "-headerUnit"
+ end
+ _g.headerunitflag = headerunitflag or false
+ end
+ return headerunitflag or nil
+end
+
+function get_exportheaderflag(target)
+ local modulesflag = get_modulesflag(target)
+ local exportheaderflag = _g.exportheaderflag
+ if exportheaderflag == nil then
+ local compinst = target:compiler("cxx")
+ if compinst:has_flags(modulesflag .. " -exportHeader", "cxxflags", {flagskey = "cl_export_header"}) then
+ exportheaderflag = "-exportHeader"
+ end
+ _g.exportheaderflag = exportheaderflag or false
+ end
+ return exportheaderflag or nil
+end
+
+function get_stdifcdirflag(target)
+ local stdifcdirflag = _g.stdifcdirflag
+ if stdifcdirflag == nil then
+ local compinst = target:compiler("cxx")
+ if compinst:has_flags("-stdIfcDir", "cxxflags", {flagskey = "cl_std_ifc_dir"}) then
+ stdifcdirflag = "-stdIfcDir"
+ end
+ _g.stdifcdirflag = stdifcdirflag or false
+ end
+ return stdifcdirflag or nil
+end
+
+function get_scandependenciesflag(target)
+ local scandependenciesflag = _g.scandependenciesflag
+ if scandependenciesflag == nil then
+ local compinst = target:compiler("cxx")
+ local scan_dependencies_jsonfile = os.tmpfile() .. ".json"
+ if compinst:has_flags("-scanDependencies " .. scan_dependencies_jsonfile, "cxflags", {flagskey = "cl_scan_dependencies",
+ on_check = function (ok, errors)
+ if os.isfile(scan_dependencies_jsonfile) then
+ ok = true
+ end
+ if ok and not os.isfile(scan_dependencies_jsonfile) then
+ ok = false
+ end
+ return ok, errors
+ end}) then
+ scandependenciesflag = "-scanDependencies"
+ end
+ _g.scandependenciesflag = scandependenciesflag or false
+ end
+ return scandependenciesflag or nil
+end
+
+-- get requireflags from module mapper
+function get_requiresflags(target, requires, opt)
+ opt = opt or {}
+ local flags = {}
+ local modulemap = _get_modulemap_from_mapper(target)
+ -- add deps required module flags
+ for name, _ in pairs(requires) do
+ for _, dep in ipairs(target:orderdeps()) do
+ local modulemap_ = _get_modulemap_from_mapper(dep)
+ if modulemap_[name] then
+ table.join2(flags, modulemap_[name].flag)
+ table.join2(flags, modulemap_[name].deps or {})
+ goto continue
+ end
+ end
+
+ -- append target required module mapper flags
+ if modulemap[name] then
+ table.join2(flags, modulemap[name].flag)
+ table.join2(flags, modulemap[name].deps or {})
+ goto continue
+ end
+
+ ::continue::
+ end
+ local requireflags = {}
+ local contains = {}
+ for i = 1, #flags, 2 do
+ local value = flags[i + 1]
+ if not contains[value] then
+ local key = flags[i]
+ if opt.expand then
+ table.insert(requireflags, key)
+ table.insert(requireflags, value)
+ else
+ table.insert(requireflags, {key, value})
+ end
+ contains[value] = true
+ end
+ end
+ if #requireflags > 0 then
+ return requireflags
+ end
+end
diff --git a/xmake/rules/c++/modules/modules_support/stl_headers.lua b/xmake/rules/c++/modules/modules_support/stl_headers.lua
new file mode 100644
index 000000000..599e2830c
--- /dev/null
+++ b/xmake/rules/c++/modules/modules_support/stl_headers.lua
@@ -0,0 +1,153 @@
+--!A cross-platform build utility based on Lua
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-- Copyright (C) 2015-present, TBOOX Open Source Group.
+--
+-- @author Arthapz, ruki
+-- @file stl_headers.lua
+--
+
+-- imports
+import("core.base.hashset")
+
+-- the stl headers list
+function _stl_headers()
+ return {
+ "algorithm",
+ "forward_list",
+ "numbers",
+ "stop_token",
+ "any",
+ "fstream",
+ "numeric",
+ "streambuf",
+ "array",
+ "functional",
+ "optional",
+ "string",
+ "atomic",
+ "future",
+ "ostream",
+ "string_view",
+ "barrier",
+ "initializer_list",
+ "queue",
+ "bit",
+ "iomanip",
+ "random",
+ "syncstream",
+ "bitset",
+ "ios",
+ "ranges",
+ "system_error",
+ "charconv",
+ "iosfwd",
+ "ratio",
+ "thread",
+ "chrono",
+ "iostream",
+ "regex",
+ "tuple",
+ "codecvt",
+ "istream",
+ "scoped_allocator",
+ "typeindex",
+ "compare",
+ "iterator",
+ "semaphore",
+ "typeinfo",
+ "complex",
+ "latch",
+ "set",
+ "type_traits",
+ "concepts",
+ "limits",
+ "shared_mutex",
+ "unordered_map",
+ "condition_variable",
+ "list",
+ "source_location",
+ "unordered_set",
+ "coroutine",
+ "locale",
+ "span",
+ "utility",
+ "deque",
+ "map",
+ "spanstream",
+ "valarray",
+ "exception",
+ "memory",
+ "sstream",
+ "variant",
+ "execution",
+ "memory_resource",
+ "stack",
+ "vector",
+ "filesystem",
+ "mutex",
+ "version",
+ "format",
+ "new",
+ "type_traits",
+ "string_view",
+ "stdexcept",
+ "condition_variable",
+ "print",
+ "flat_map",
+ "flat_set",
+ "mdspan",
+ "stdfloat",
+ "generator",
+ "csetjmp",
+ "csignal",
+ "cstdarg",
+ "cstddef",
+ "cstdlib",
+ "cfloat",
+ "cinttypes",
+ "climits",
+ "cstdint",
+ "cassert",
+ "cerrno",
+ "cctype",
+ "cstring",
+ "cuchar",
+ "cwchar",
+ "cwctype",
+ "cfenv",
+ "cmath",
+ "ctime",
+ "clocale",
+ "cstdio"}
+end
+
+-- get all stl headers
+function get_stl_headers()
+ local stl_headers = _g.stl_headers
+ if stl_headers == nil then
+ stl_headers = hashset.from(_stl_headers())
+ _g.stl_headers = stl_headers or false
+ end
+ return stl_headers or nil
+end
+
+-- is stl header?
+function is_stl_header(header)
+ if header:startswith("experimental/") then
+ header = header:sub(14, -1)
+ end
+ return get_stl_headers():has(header)
+end
+