diff options
| author | ruki <[email protected]> | 2022-08-16 12:33:47 +0800 |
|---|---|---|
| committer | GitHub <[email protected]> | 2022-08-16 12:33:47 +0800 |
| commit | 81aca6fbe9149b377cdd88e8ee4b8f045d5a46da (patch) | |
| tree | 0f138f960ab9fd95265ecbffca7c6ae27d65f4a3 /xmake/rules/c++/modules/modules_support | |
| parent | d46b16f8c40493bfc78764931bf7c3209442696b (diff) | |
| parent | d9f82e1bba29adc8b11dc1f7b3887717f03137d6 (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.lua | 603 | ||||
| -rw-r--r-- | xmake/rules/c++/modules/modules_support/common.lua | 555 | ||||
| -rw-r--r-- | xmake/rules/c++/modules/modules_support/gcc.lua | 457 | ||||
| -rw-r--r-- | xmake/rules/c++/modules/modules_support/msvc.lua | 688 | ||||
| -rw-r--r-- | xmake/rules/c++/modules/modules_support/stl_headers.lua | 153 |
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 + |
