From 7788ec532d3730f0a521bb0634a25da12b5bc081 Mon Sep 17 00:00:00 2001 From: Arthur LAURENT Date: Sun, 31 Jul 2022 15:21:35 +0200 Subject: Improve C++20 modules support --- xmake/rules/c++/modules/xmake.lua | 148 +++++++++++++++++++++++++++++++++----- 1 file changed, 130 insertions(+), 18 deletions(-) (limited to 'xmake/rules/c++/modules/xmake.lua') diff --git a/xmake/rules/c++/modules/xmake.lua b/xmake/rules/c++/modules/xmake.lua index 12132da54..0f03b9b13 100644 --- a/xmake/rules/c++/modules/xmake.lua +++ b/xmake/rules/c++/modules/xmake.lua @@ -20,46 +20,158 @@ -- define rule: c++.build.modules rule("c++.build.modules") - set_extensions(".mpp", ".mxx", ".cppm", ".ixx") + add_deps("c++.build.modules.builder") + on_config(function (target) - -- we disable to build across targets in parallel, because the source files may depend on other target modules - -- @see https://github.com/xmake-io/xmake/issues/1858 - local target_with_modules + local target_with_modules = (target:modulefiles() and #target:modulefiles() > 0) and true or false + for _, dep in ipairs(target:orderdeps()) do - local sourcebatches = dep:sourcebatches() - if sourcebatches and sourcebatches["c++.build.modules"] then + local modulefiles = dep:get("modulefiles") + if modulefiles and #modulefiles > 0 then target_with_modules = true break end end + if target_with_modules then - -- @note this will cause cross-parallel builds to be disabled for all sub-dependent targets, - -- even if some sub-targets do not contain C++ modules. - -- - -- maybe we will have a more fine-grained configuration strategy to disable it in the future. target:set("policy", "build.across_targets_in_parallel", false) + + -- import build_modules + local build_modules if target:has_tool("cxx", "clang", "clangxx") then - import("build_modules.clang").load_parent(target, opt) + build_modules = import("build_modules.clang") elseif target:has_tool("cxx", "gcc", "gxx") then - import("build_modules.gcc").load_parent(target, opt) + build_modules = import("build_modules.gcc") elseif target:has_tool("cxx", "cl") then - import("build_modules.msvc").load_parent(target, opt) + build_modules = import("build_modules.msvc") else local _, toolname = target:tool("cxx") raise("compiler(%s): does not support c++ module!", toolname) end + + -- check C++20 module support + build_modules.check_module_support(target) + + -- load parent + build_modules.load_parent(target, opt) + + for _, modulefile in ipairs(target:modulefiles()) do + target:add("files", modulefile) + end + + target:set("cxx.has_modules", true) + end + end) + + before_build(function(target, opt) + if not target:get("cxx.has_modules") then + return + end + + local build_modules + if target:has_tool("cxx", "clang", "clangxx") then + build_modules = import("build_modules.clang") + elseif target:has_tool("cxx", "gcc", "gxx") then + build_modules = import("build_modules.gcc") + elseif target:has_tool("cxx", "cl") then + build_modules = import("build_modules.msvc") + else + local _, toolname = target:tool("cxx") + raise("compiler(%s): does not support c++ module!", toolname) + end + + -- build dependency data + local common = import("build_modules.common") + + local moduleinfos = {} + for _, sourcebatch in pairs(target:sourcebatches()) do + local batch = sourcebatch + + if batch.rulename == "c++.build.modules.builder" then + batch.objectfiles = {} + batch.dependfiles = {} + common.patch_sourcebatch(target, batch, opt) + end + + if batch.rulename:startswith("c++.build") then + build_modules.generate_dependencies(target, batch, opt) + local infos = common.load(target, batch, opt) + + table.join2(moduleinfos, infos or {}) + end end + + local modules = common.parse_dependency_data(target, moduleinfos, opt) + + target:data_set("cxx.modules", modules) end) - before_build_files(function (target, batchjobs, sourcebatch, opt) + + +rule("c++.build.modules.builder") + set_sourcekinds("cxx") + set_extensions(".mpp", ".mxx", ".cppm", ".ixx") + + before_buildcmd_files(function(target, batchcmds, sourcebatch, opt) + if not target:get("cxx.has_modules") then + return + end + + local build_modules if target:has_tool("cxx", "clang", "clangxx") then - import("build_modules.clang").build_with_batchjobs(target, batchjobs, sourcebatch, opt) + build_modules = import("build_modules.clang") elseif target:has_tool("cxx", "gcc", "gxx") then - import("build_modules.gcc").build_with_batchjobs(target, batchjobs, sourcebatch, opt) + build_modules = import("build_modules.gcc") elseif target:has_tool("cxx", "cl") then - import("build_modules.msvc").build_with_batchjobs(target, batchjobs, sourcebatch, opt) + build_modules = import("build_modules.msvc") else local _, toolname = target:tool("cxx") raise("compiler(%s): does not support c++ module!", toolname) end - end, {batch = true}) + local common = import("build_modules.common") + + local batch = sourcebatch + + batch.objectfiles = {} + batch.dependfiles = {} + common.patch_sourcebatch(target, batch, opt) + + local modules = target:data("cxx.modules") + + local headerunits + for _, objectfile in ipairs(batch.objectfiles) do + for obj, m in pairs(modules) do + if obj == objectfile then + for name, r in pairs(m.requires) do + if r.method ~= "by-name" then + headerunits = headerunits or {} + + local type = r.method == "include-angle" and ":angle" or ":quote" + table.append(headerunits, { name = name, path = r.path, type = type, stl = common.is_stl_header(name) }) + end + end + break + end + end + end + + local headerunits_flags + local private_headerunits_flags + if headerunits then + headerunits_flags, private_headerunits_flags = build_modules.generate_headerunits(target, batchcmds, headerunits, opt) + end + + if headerunits_flags then + target:add("cxxflags", headerunits_flags, {force = true, expand = false}) + end + if private_headerunits_flags then + target:add("cxxflags", private_headerunits_flags, {force = true, expand = false}) + end + + local modules = target:data("cxx.modules") + + -- topological sort + local objectfiles = common.sort_modules_by_dependencies(batch.objectfiles, modules) + + build_modules.build_modules(target, batchcmds, objectfiles, modules, opt) + end) \ No newline at end of file -- cgit v1.3.1 From ab70577eccc2f4b003041e5c8850a775ddc81be0 Mon Sep 17 00:00:00 2001 From: Arthur LAURENT Date: Tue, 2 Aug 2022 18:32:58 +0200 Subject: [C++20 Modules] Improve code --- xmake/rules/c++/modules/xmake.lua | 52 ++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 25 deletions(-) (limited to 'xmake/rules/c++/modules/xmake.lua') diff --git a/xmake/rules/c++/modules/xmake.lua b/xmake/rules/c++/modules/xmake.lua index 0f03b9b13..8992e3133 100644 --- a/xmake/rules/c++/modules/xmake.lua +++ b/xmake/rules/c++/modules/xmake.lua @@ -20,15 +20,18 @@ -- define rule: c++.build.modules rule("c++.build.modules") + set_extensions(".mpp", ".mxx", ".cppm", ".ixx") + add_deps("c++.build.modules.dependencies") add_deps("c++.build.modules.builder") + add_deps("c++.build.modules.install") on_config(function (target) - local target_with_modules = (target:modulefiles() and #target:modulefiles() > 0) and true or false + local target_with_modules = target:sourcebatches()["c++.build.modules"] and true or false for _, dep in ipairs(target:orderdeps()) do local modulefiles = dep:get("modulefiles") if modulefiles and #modulefiles > 0 then - target_with_modules = true + target_with_modules = target:sourcebatches()["c++.build.modules"] and true or target_with_modules break end end @@ -55,14 +58,15 @@ rule("c++.build.modules") -- load parent build_modules.load_parent(target, opt) - for _, modulefile in ipairs(target:modulefiles()) do - target:add("files", modulefile) - end - target:set("cxx.has_modules", true) end end) + +rule("c++.build.modules.dependencies") + set_sourcekinds("cxx") + set_extensions(".mpp", ".mxx", ".cppm", ".ixx") + before_build(function(target, opt) if not target:get("cxx.has_modules") then return @@ -80,33 +84,20 @@ rule("c++.build.modules") raise("compiler(%s): does not support c++ module!", toolname) end - -- build dependency data local common = import("build_modules.common") - local moduleinfos = {} - for _, sourcebatch in pairs(target:sourcebatches()) do - local batch = sourcebatch - - if batch.rulename == "c++.build.modules.builder" then - batch.objectfiles = {} - batch.dependfiles = {} - common.patch_sourcebatch(target, batch, opt) - end - - if batch.rulename:startswith("c++.build") then - build_modules.generate_dependencies(target, batch, opt) - local infos = common.load(target, batch, opt) + -- build dependency data + local batch = target:sourcebatches()["c++.build.modules.dependencies"] + common.patch_sourcebatch(target, batch, opt) - table.join2(moduleinfos, infos or {}) - end - end + build_modules.generate_dependencies(target, batch, opt) + local moduleinfos = common.load(target, batch, opt) local modules = common.parse_dependency_data(target, moduleinfos, opt) target:data_set("cxx.modules", modules) end) - rule("c++.build.modules.builder") set_sourcekinds("cxx") set_extensions(".mpp", ".mxx", ".cppm", ".ixx") @@ -131,6 +122,8 @@ rule("c++.build.modules.builder") local common = import("build_modules.common") local batch = sourcebatch + print(sourcebatch) + print(batch) batch.objectfiles = {} batch.dependfiles = {} @@ -174,4 +167,13 @@ rule("c++.build.modules.builder") local objectfiles = common.sort_modules_by_dependencies(batch.objectfiles, modules) build_modules.build_modules(target, batchcmds, objectfiles, modules, opt) - end) \ No newline at end of file + end) + +rules("c++.build.modules.install") + set_extensions(".mpp", ".mxx", ".cppm", ".ixx") + + on_config(function (target) + local sourcebatch = target:sourcebatches()["c++.build.modules.install"] + + target:add("installfiles", sourcebatch.sourcefiles, {prefixdir = "include"}) + end \ No newline at end of file -- cgit v1.3.1 From 92b8b6629b6c3b4b59d78835b7e4719313c04ae1 Mon Sep 17 00:00:00 2001 From: Arthur LAURENT Date: Wed, 3 Aug 2022 02:35:01 +0200 Subject: [C++20 Modules] Improve support on MSVC Clang and GCC --- xmake/rules/c++/modules/build_modules/clang.lua | 210 ---------- xmake/rules/c++/modules/build_modules/common.lua | 409 -------------------- xmake/rules/c++/modules/build_modules/gcc.lua | 232 ----------- xmake/rules/c++/modules/build_modules/msvc.lua | 249 ------------ xmake/rules/c++/modules/modules_support/clang.lua | 270 +++++++++++++ xmake/rules/c++/modules/modules_support/common.lua | 423 +++++++++++++++++++++ xmake/rules/c++/modules/modules_support/gcc.lua | 259 +++++++++++++ xmake/rules/c++/modules/modules_support/msvc.lua | 327 ++++++++++++++++ xmake/rules/c++/modules/xmake.lua | 64 ++-- 9 files changed, 1308 insertions(+), 1135 deletions(-) delete mode 100644 xmake/rules/c++/modules/build_modules/clang.lua delete mode 100644 xmake/rules/c++/modules/build_modules/common.lua delete mode 100644 xmake/rules/c++/modules/build_modules/gcc.lua delete mode 100644 xmake/rules/c++/modules/build_modules/msvc.lua create mode 100644 xmake/rules/c++/modules/modules_support/clang.lua create mode 100644 xmake/rules/c++/modules/modules_support/common.lua create mode 100644 xmake/rules/c++/modules/modules_support/gcc.lua create mode 100644 xmake/rules/c++/modules/modules_support/msvc.lua (limited to 'xmake/rules/c++/modules/xmake.lua') diff --git a/xmake/rules/c++/modules/build_modules/clang.lua b/xmake/rules/c++/modules/build_modules/clang.lua deleted file mode 100644 index bce008766..000000000 --- a/xmake/rules/c++/modules/build_modules/clang.lua +++ /dev/null @@ -1,210 +0,0 @@ ---!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.base.json") -import("core.project.config") -import("utils.progress") -import("private.action.build.object", {alias = "objectbuilder"}) - -local default_flags = { "-std=c++20" } - --- load parent target with modules files -function load_parent(target, opt) - local common = import("common") - local cachedir = common.get_cache_dir(target) - local stlcachedir = common.get_stlcache_dir(target) - - -- add module flags - target:add("cxxflags", "-fmodules") - - -- add the module cache directory - target:add("cxxflags", "-fimplicit-modules", "-fimplicit-module-maps", {force = true}) - target:add("cxxflags", "-fprebuilt-module-path=" .. cachedir, "-fprebuilt-module-path=" .. stlcachedir, {force = true}) - - for _, dep in ipairs(target:orderdeps()) do - cachedir = common.get_cache_dir(dep) - dep:add("cxxflags", "-fmodules") - dep:add("cxxflags", "-fimplicit-modules", "-fimplicit-module-maps", {force = true}) - target:add("cxxflags", "-fprebuilt-module-path=" .. cachedir, "-fprebuilt-module-path=" .. stlcachedir, {force = true}) - target:add("cxxflags", "-fprebuilt-module-path=" .. cachedir, {force = true}) - end -end - --- check C++20 module support -function check_module_support(target) - local modulesflag - local compinst = compiler.load("cxx", {target = target}) - if compinst:has_flags("-fmodules") then - modulesflag = "-fmodules" - end - assert(modulesflag, "compiler(clang): does not support c++ module!") - - target:data_set("cxx.has_p1689r4", false) -end - -function generate_dependencies(target, sourcebatch, opt) - local common = import("common") - local cachedir = common.get_cache_dir(target) - - for _, sourcefile in ipairs(sourcebatch.sourcefiles) do - local dependfile = target:dependfile(sourcefile) - depend.on_changed(function() - progress.show(opt.progress, "${color.build.object}generating.cxx.module.deps %s", sourcefile) - - local outdir = path.translate(path.join(cachedir, path.directory(path.relative(sourcefile, target:scriptdir())))) - if not os.isdir(outdir) then - os.mkdir(outdir) - end - - local jsonfile = path.translate(path.join(outdir, path.filename(sourcefile) .. ".json")) - - if target:data("cxx.has_p1689r4") then - else -- fallback as clang doesn't support p1689r4 - local dependinfo = common.fallback_generate_dependencies(target, jsonfile, sourcefile) - local jsondata = json.encode(dependinfo) - - io.writefile(jsonfile, jsondata) - end - - local dependinfo = io.readfile(jsonfile) - - return { moduleinfo = dependinfo } - end, {dependfile = dependfile, files = {sourcefile}}) - end -end - --- generate target header units -function generate_headerunits(target, batchcmds, sourcebatch, opt) - local common = import("common") - - local compinst = target:compiler("cxx") - - local cachedir = common.get_cache_dir(target) - local stlcachedir = common.get_stlcache_dir(target) - - -- build headerunits - local objectfiles = {} - local public_flags = {} - local private_flags = {} - for _, headerunit in ipairs(sourcebatch) do - if not headerunit.stl then - local file = path.relative(headerunit.path, target:scriptdir()) - - local objectfile = target:objectfile(file) - - local outdir = path.join(cachedir, "include", path.directory(headerunit.name)) - if not os.isdir(outdir) then - os.mkdir(outdir) - end - - local bmifilename = path.basename(objectfile) .. ".pcm" - - local bmifile = (outdir and path.join(outdir, bmifilename) or bmifilename) - if not os.isdir(path.directory(objectfile)) then - os.mkdir(path.directory(objectfile)) - end - - local args = { "-fmodules-cache-path=" .. cachedir, "-emit-module", "-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 - - 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}) or default_flags, args)) - - batchcmds:add_depfiles(headerunit.path) - batchcmds:set_depmtime(os.mtime(bmifile)) - batchcmds:set_depcache(target:dependfile(bmifile)) - - table.append(public_flags, "-fmodule-file=" .. bmifile) - else - local bmifile = path.join(stlcachedir, headerunit.name .. ".pcm") - - if not os.isfile(bmifile) then - local args = { "-fmodules-cache-path=" .. stlcachedir, "-c", "-o", bmifile, "-x", "c++-system-header", headerunit.path } - - 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}) or default_flags, args)) - - end - - batchcmds:set_depmtime(os.mtime(bmifile)) - batchcmds:set_depcache(target:dependfile(bmifile)) - - table.append(private_flags, "-fmodule-file=" .. bmifile) - end - end - - return public_flags, private_flags -end - --- build module files -function build_modules(target, batchcmds, objectfiles, modules, opt) - local cachedir = common.get_cache_dir(target) - - local compinst = target:compiler("cxx") - - -- append deps modules - local flags = {} - for _, dep in ipairs(target:orderdeps()) do - table.join2(flags, dep:data("cxx.modules.flags")) - end - flags = table.unique(flags) - target:add("cxxflags", flags, {force = true, expand = false}) - - local common_args = { "-fmodules-cache-path=" .. cachedir, "-emit-module-interface" } - for _, objectfile in ipairs(objectfiles) do - local m = modules[objectfile] - - if m then - if not os.isdir(path.directory(objectfile)) then - os.mkdir(path.directory(objectfile)) - end - - local args = { } - local flag = {} - for name, provide in pairs(m.provides) do - batchcmds:show_progress(opt.progress, "${color.build.object}generating.cxx.module.bmi %s", name) - - local bmifile = provide.bmi - table.join2(args, { "-c", "-x", "c++-module", "--precompile", provide.sourcefile, "-o", bmifile }) - - batchcmds:add_depfiles(provide.sourcefile) - batchcmds:set_depmtime(os.mtime(bmifile)) - batchcmds:set_depcache(target:dependfile(bmifile)) - - table.join2(flag, { "-fmodule-file=" .. bmifile }) - end - - batchcmds:vrunv(compinst:program(), table.join(compinst:compflags({target = target}) or default_flags, common_args, args)) - - target:add("cxxflags", flag, {public = true, force = true}) - for _, f in ipairs(flag) do - target:data_add("cxx.modules.flags", f) - end - end - end -end diff --git a/xmake/rules/c++/modules/build_modules/common.lua b/xmake/rules/c++/modules/build_modules/common.lua deleted file mode 100644 index bf64386e4..000000000 --- a/xmake/rules/c++/modules/build_modules/common.lua +++ /dev/null @@ -1,409 +0,0 @@ -import("core.base.json") -import("core.project.config") -import("core.tool.compiler") -import("core.cache.globalcache") -import("core.project.project") -import("lib.detect") -import("lib.detect.find_package") -import("lib.detect.find_file") - -local stl_headers = { - "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"} - -function get_stl_headers() - return stl_headers -end - -function is_stl_header(header) - for _, stl_header in ipairs(stl_headers) do - if stl_header == header then - return true - end - end - - return false -end - -function get_stlcache_dir(target) - local stlcachedir = path.join(target:autogendir(), "stlmodules", "cache") - if target:has_tool("cxx", "clang", "clangxx") then - stlcachedir = path.join(config.buildir(), "stlmodules", "cache") - end - if not os.isdir(stlcachedir) then - os.mkdir(stlcachedir) - end - - return stlcachedir -end - -function get_cache_dir(target) - local cachedir = path.join(target:autogendir(), "rules", "modules", "cache") - if not os.isdir(cachedir) then - os.mkdir(cachedir) - end - - return cachedir -end - -function patch_sourcebatch(target, sourcebatch, opt) - local cachedir = get_cache_dir(target) - - sourcebatch.sourcekind = "cxx" - sourcebatch.objectfiles = sourcebatch.objectfiles or {} - sourcebatch.dependfiles = sourcebatch.dependfiles or {} - - 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 - -function get_bmi_ext(target) - if target:has_tool("cxx", "gcc", "gxx") then - return ".gcm" - elseif target:has_tool("cxx", "cl") then - return ".ifc" - elseif target:has_tool("cxx", "clang", "clangxx") then - return ".pcm" - end - - assert(false) -end - -function load(target, sourcebatch, opt) - local cachedir = get_cache_dir(target) - - 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.append(moduleinfos, moduleinfo) - end - end - end - end - - return moduleinfos -end - -function parse_dependency_data(target, moduleinfos, opt) - local cachedir = get_cache_dir(target) - - local modules - for _, moduleinfo in ipairs(moduleinfos) do - assert(moduleinfo.version <= 1) - for _, rule in ipairs(moduleinfo.rules) do - modules = modules or {} - - local m = {} - - for _, provide in ipairs(rule.provides) do - m.provides = m.provides or {} - - if provide["compiled-module-path"] then - if not path.is_absolute(provide["compiled-module-path"]) then - m.provides[provide["logical-name"] ] = path.absolute(provide["compiled-module-path"]) - else - m.provides[provide["logical-name"] ] = provide["compiled-module-path"] - end - else -- assume path with name - local name = provide["logical-name"] .. get_bmi_ext(target) - name:replace(":", "-") - - m.provides[provide["logical-name"] ] = { - bmi = path.join(cachedir, name), - sourcefile = moduleinfo.sourcefile - } - end - end - - modules[rule["primary-output"] ] = m - end - end - - for _, moduleinfo in ipairs(moduleinfos) do - for _, rule in ipairs(moduleinfo.rules) do - local m = modules[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, - 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] - - assert(m1.provides) - for _, n in ipairs(nodes) do - if not n.tempmarked then - local m2 = modules[n.objectfile] - - 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 - - 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 - -function sort_modules_by_dependencies(objectfiles, modules) - local output = {} - - local nodes = {} - for _, objectfile in ipairs(objectfiles) do - local m = modules[objectfile] - - if m.provides then - table.append(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) - -- check if the header is in subtarget - - local headerpaths = {} - - for _, dep in ipairs(target:orderdeps()) do - table.append(headerpaths, dep:scriptdir()) - end - - if project.required_packages then - for _, name in ipairs(target:get("packages")) do - local package = project.required_package(name) - table.join2(headerpaths, package:get("sysincludedirs")) - end - end - - table.join2(headerpaths, target:get("includedirs")) - - local p = find_file(file, table.join(headerpaths, { "/usr/include/**", "/usr/local/include/**" })) - - assert(os.isfile(p)) - - return p -end - -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*;") - - 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.append(rule.outputs, module_name .. get_bmi_ext(target)) - - local provide = {} - provide["logical-name"] = module_name - provide["source-path"] = path.absolute(sourcefile, project.directory()) - - rule.provides = {} - table.append(rule.provides, provide) - end - - rule.requires = module_deps - - table.append(output.rules, rule) - - return output -end \ No newline at end of file diff --git a/xmake/rules/c++/modules/build_modules/gcc.lua b/xmake/rules/c++/modules/build_modules/gcc.lua deleted file mode 100644 index 97bb9cc9a..000000000 --- a/xmake/rules/c++/modules/build_modules/gcc.lua +++ /dev/null @@ -1,232 +0,0 @@ ---!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.base.json") -import("core.project.config") -import("utils.progress") -import("private.action.build.object", {alias = "objectbuilder"}) - -local default_flags = {"-std=c++20"} - -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 - -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:printf("%s %s\n", module, bmi) - f:close() - - return true -end - --- load parent target with modules files -function load_parent(target, opt) - local common = import("common") - local cachedir = common.get_cache_dir(target) - - target:add("cxxflags", "-fmodules-ts") - if os.isfile(get_module_mapper()) then - os.rm(get_module_mapper()) - end - target:add("cxxflags", "-fmodule-mapper=" .. get_module_mapper(), {force = true, expand = false}) - - for _, dep in ipairs(target:orderdeps()) do - cachedir = common.get_cache_dir(dep) - dep:add("cxxflags", "-fmodules-ts") - dep:add("cxxflags", "-fmodule-mapper=" .. get_module_mapper(), {force = true, expand = false}) - end -end - --- check C++20 module support -function check_module_support(target) - local modulesflag - local compinst = compiler.load("cxx", {target = target}) - if compinst:has_flags("-fmodules-ts") then - modulesflag = "-fmodules-ts" - end - assert(modulesflag, "compiler(gcc): does not support c++ module!") - - if compinst:has_flags("-fdep-format=trtbd") then - target:data_set("cxx.has_p1689r4", true) - end -end - -function generate_dependencies(target, sourcebatch, opt) - local common = import("common") - local cachedir = common.get_cache_dir(target) - local compinst = target:compiler("cxx") - local common_args = {"-E", "-x", "c++"} - - for _, sourcefile in ipairs(sourcebatch.sourcefiles) do - local dependfile = target:dependfile(sourcefile) - depend.on_changed(function() - progress.show(opt.progress, "${color.build.object}generating.cxx.module.deps %s", sourcefile) - - local outdir = path.translate(path.join(cachedir, path.directory(path.relative(sourcefile, target:scriptdir())))) - if not os.isdir(outdir) then - os.mkdir(outdir) - end - - local jsonfile = path.translate(path.join(outdir, path.filename(sourcefile) .. ".json")) - - if target:data("cxx.has_p1689r4") then - local ifile = path.translate(path.join(outdir, path.filename(sourcefile) .. ".i")) - local dfile = path.translate(path.join(outdir, path.filename(sourcefile) .. ".d")) - - local args = {sourcefile, "-MD", "-MT", jsonfile, "-MF", dfile, "-fdep-file=" .. jsonfile, "-fdep-format=trtbd", "-fdep-output=" .. target:objectfile(sourcefile), "-o", ifile} - - os.vrunv(compinst:program(), table.join(compinst:compflags({target = target}) or default_flags, common_args, args), {envs = vcvars}) - else -- fallback as GCC doesn't fully support p1689r4 - local dependinfo = common.fallback_generate_dependencies(target, jsonfile, sourcefile) - local jsondata = json.encode(dependinfo) - - io.writefile(jsonfile, jsondata) - end - - local dependinfo = io.readfile(jsonfile) - - return { moduleinfo = dependinfo } - end, {dependfile = dependfile, files = {sourcefile}}) - end -end - --- generate target header units -function generate_headerunits(target, batchcmds, sourcebatch, opt) - local common = import("common") - - local compinst = target:compiler("cxx") - - local cachedir = common.get_cache_dir(target) - local stlcachedir = common.get_stlcache_dir(target) - - local mapper_file = get_module_mapper() - - -- build headerunits - local objectfiles = {} - for _, headerunit in ipairs(sourcebatch) do - if not headerunit.stl then - local file = path.relative(headerunit.path, target:scriptdir()) - - local objectfile = target:objectfile(file) - - local outdir = path.join(cachedir, "include", path.directory(headerunit.name)) - if not os.isdir(outdir) then - os.mkdir(outdir) - end - - local bmifilename = path.basename(objectfile) .. ".gcm" - - local bmifile = (outdir and path.join(outdir, bmifilename) or bmifilename) - if not os.isdir(path.directory(objectfile)) then - os.mkdir(path.directory(objectfile)) - end - - if add_module_to_mapper(mapper_file, headerunit.path, path.absolute(bmifile, project.directory())) then - local args = { "-c" } - if headerunit.type == ":quote" then - table.join2(args, { "-I", path.directory(headerunit.path), "-x", "c++-user-header", headerunit.name }) - add_module_to_mapper(mapper_file, path.join(".", path.relative(headerunit.path, project.directory())), path.absolute(bmifile, project.directory())) - elseif headerunit.type == ":angle" then - table.join2(args, { "-x", "c++-system-header", headerunit.name }) - add_module_to_mapper(mapper_file, headerunit.name, path.absolute(bmifile, project.directory())) - 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}) or default_flags, args)) - - batchcmds:add_depfiles(headerunit.path) - batchcmds:set_depmtime(os.mtime(bmifile)) - batchcmds:set_depcache(target:dependfile(bmifile)) - end - else - local bmifile = path.join(stlcachedir, headerunit.name .. ".gcm") - - if add_module_to_mapper(mapper_file, headerunit.path, path.absolute(bmifile, project.directory())) then - 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}) or default_flags, args)) - - batchcmds:set_depmtime(os.mtime(bmifile)) - batchcmds:set_depcache(target:dependfile(bmifile)) - end - end - end - - end -end - --- build module files -function build_modules(target, batchcmds, objectfiles, modules, opt) - local cachedir = common.get_cache_dir(target) - - local compinst = target:compiler("cxx") - - local mapper_file = get_module_mapper() - local common_args = { "-x", "c++" } - for _, objectfile in ipairs(objectfiles) do - local m = modules[objectfile] - - if m then - if not os.isdir(path.directory(objectfile)) then - os.mkdir(path.directory(objectfile)) - end - - local args = { "-o", objectfile } - for name, provide in pairs(m.provides) do - batchcmds:show_progress(opt.progress, "${color.build.object}generating.cxx.module.bmi %s", name) - - local bmifile = provide.bmi - if add_module_to_mapper(mapper_file, name, path.absolute(bmifile, project.directory())) then - table.join2(args, { "-c", provide.sourcefile }) - - batchcmds:add_depfiles(provide.sourcefile) - batchcmds:set_depmtime(os.mtime(bmifile)) - batchcmds:set_depcache(target:dependfile(bmifile)) - end - end - - batchcmds:vrunv(compinst:program(), table.join(compinst:compflags({target = target}) or default_flags, common_args, args)) - - batchcmds:set_depmtime(os.mtime(objectfile)) - batchcmds:set_depcache(target:dependfile(objectfile)) - - target:add("objectfiles", objectfile) - end - end -end - diff --git a/xmake/rules/c++/modules/build_modules/msvc.lua b/xmake/rules/c++/modules/build_modules/msvc.lua deleted file mode 100644 index ac1386a27..000000000 --- a/xmake/rules/c++/modules/build_modules/msvc.lua +++ /dev/null @@ -1,249 +0,0 @@ ---!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 msvc.lua --- - --- imports -import("core.tool.compiler") -import("core.project.depend") -import("utils.progress") -import("private.action.build.object", {alias = "objectbuilder"}) - -local default_flags = {"/EHsc", "/nologo", "/std:c++20", "/experimental:module"} - --- load parent target with modules files -function load_parent(target, opt) - local common = import("common") - -- get modules flag - local compinst = target:compiler("cxx") - - -- add module flags - local cachedir = common.get_cache_dir(target) - local stlcachedir = common.get_stlcache_dir(target) - - target:add("cxxflags", "/experimental:module") - target:add("cxxflags", {"/ifcSearchDir", cachedir}, {force = true, expand = false}) - target:add("cxxflags", {"/ifcSearchDir", stlcachedir}, {force = true, expand = false}) - - for _, dep in ipairs(target:orderdeps()) do - cachedir = common.get_cache_dir(dep) - dep:add("cxxflags", "/experimental:module") - target:add("cxxflags", {"/ifcSearchDir", cachedir}, {force = true, expand = false}) - end - - 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", {"/stdIfcDir", winos.short_path(stdifcdir)}, {force = true, expand = false}) - end - end - break - end - end -end - --- check C++20 module support -function check_module_support(target) - local compinst = target:compiler("cxx") - - if compinst:has_flags("/experimental:module", "cxxflags") then - modulesflag = "/experimental:module" - end - assert(modulesflag, "compiler(msvc): does not support c++ module!") - - -- get output flag - local outputflag - if compinst:has_flags("/ifcOutput", "cxxflags") then - outputflag = "/ifcOutput" - end - assert(outputflag, "compiler(msvc): does not support c++ module!") - - -- get interface flag - local interfaceflag - if compinst:has_flags("/interface", "cxxflags") then - interfaceflag = "/interface" - end - assert(interfaceflag, "compiler(msvc): does not support c++ module!") - - -- get reference flag - local referenceflag - if compinst:has_flags("/reference", "cxxflags") then - referenceflag = "/reference" - end - assert(referenceflag, "compiler(msvc): does not support c++ module!") - - -- get stdifcdir flag - local stdifcdirflag - if compinst:has_flags("/stdIfcDir", "cxxflags") then - stdifcdirflag = "/stdIfcDir" - end - assert(stdifcdirflag, "compiler(msvc): does not support c++ module!") -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 common = import("common") - local cachedir = common.get_cache_dir(target) - local common_args = {"/TP", "/scanDependencies"} - - for _, sourcefile in ipairs(sourcebatch.sourcefiles) do - local dependfile = target:dependfile(sourcefile) - depend.on_changed(function() - progress.show(opt.progress, "${color.build.object}generating.cxx.module.deps %s", sourcefile) - - local outdir = path.join(cachedir, path.directory(path.relative(sourcefile, target:scriptdir()))) - if not os.isdir(outdir) then - os.mkdir(outdir) - end - - local jsonfile = path.join(outdir, path.filename(sourcefile) .. ".json") - local args = {jsonfile, sourcefile, "/Fo" .. target:objectfile(sourcefile)} - - os.vrunv(compinst:program(), table.join(compinst:compflags({target = target}) or default_flags, common_args, args), {envs = vcvars}) - - local dependinfo = io.readfile(jsonfile) - - return { moduleinfo = dependinfo } - end, {dependfile = dependfile, files = {sourcefile}}) - end -end - --- generate target header units -function generate_headerunits(target, batchcmds, sourcebatch, opt) - local common = import("common") - - local compinst = target:compiler("cxx") - local toolchain = target:toolchain("msvc") - local vcvars = toolchain:config("vcvars") - - local cachedir = common.get_cache_dir(target) - local stlcachedir = common.get_stlcache_dir(target) - - -- build headerunits - local common_args = {"/TP", "/exportHeader", "/c"} - local objectfiles = {} - local public_flags = {} - local private_flags = {} - for _, headerunit in ipairs(sourcebatch) do - if not headerunit.stl then - local file = path.relative(headerunit.path, target:scriptdir()) - - local objectfile = target:objectfile(file) - - local outdir = path.join(cachedir, "include", path.directory(headerunit.name)) - if not os.isdir(outdir) then - os.mkdir(outdir) - end - - local bmifilename = path.basename(objectfile) .. ".ifc" - - local bmifile = (outdir and path.join(outdir, bmifilename) or bmifilename) - if not os.isdir(path.directory(objectfile)) then - os.mkdir(path.directory(objectfile)) - end - - local args = {"/headerName" .. headerunit.type, headerunit.path, "/ifcOutput", outdir, "/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}) or default_flags, common_args, args), {envs = vcvars}) - - batchcmds:add_depfiles(headerunit.path) - batchcmds:set_depmtime(os.mtime(bmifile)) - batchcmds:set_depcache(target:dependfile(bmifile)) - batchcmds:set_depmtime(os.mtime(objectfile)) - batchcmds:set_depcache(target:dependfile(objectfile)) - - local flag = {"/headerUnit" .. headerunit.type, headerunit.name .. "=" .. path.relative(bmifile, cachedir)} - table.join2(public_flags, flag) - target:add("objectfiles", objectfile) - else - local bmifile = path.join(stlcachedir, headerunit.name .. ".ifc") - local args = {"/exportHeader", "/headerName:angle", headerunit.name, "/ifcOutput", stlcachedir} - 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}) or default_flags, args), {envs = vcvars}) - - batchcmds:set_depmtime(os.mtime(bmifile)) - batchcmds:set_depcache(target:dependfile(bmifile)) - - local flag = {"/headerUnit:angle", headerunit.name .. "=" .. headerunit.name .. ".ifc"} - table.join2(private_flags, flag) - end - end - - return public_flags, private_flags -end - --- build module files -function build_modules(target, batchcmds, objectfiles, modules, opt) - local cachedir = common.get_cache_dir(target) - - local compinst = target:compiler("cxx") - local toolchain = target:toolchain("msvc") - local vcvars = toolchain:config("vcvars") - - -- append deps modules - for _, dep in ipairs(target:orderdeps()) do - target:add("cxxflags", dep:data("cxx.modules.flags"), {force = true, expand = false}) - end - - -- compile module files to bmi files - local common_args = {"/TP"} - for _, objectfile in ipairs(objectfiles) do - local m = modules[objectfile] - - if m then - if not os.isdir(path.directory(objectfile)) then - os.mkdir(path.directory(objectfile)) - end - - local args = {"/c", "/Fo" .. objectfile} - - local flag = {} - for name, provide in pairs(m.provides) do - batchcmds:show_progress(opt.progress, "${color.build.object}generating.cxx.module.bmi %s", name) - - local bmifile = provide.bmi - table.join2(args, {"/interface", "/ifcOutput", bmifile, provide.sourcefile}) - - batchcmds:add_depfiles(provide.sourcefile) - batchcmds:set_depmtime(os.mtime(bmifile)) - batchcmds:set_depcache(target:dependfile(bmifile)) - - flag = {"/reference", name .. "=" .. path.filename(bmifile)} - end - - batchcmds:vrunv(compinst:program(), table.join(compinst:compflags({target = target}) or default_flags, common_args, args, bmi_args), {envs = vcvars}) - - batchcmds:set_depmtime(os.mtime(objectfile)) - batchcmds:set_depcache(target:dependfile(objectfile)) - - target:add("cxxflags", flag, {force = true, expand = false}) - for _, f in ipairs(flag) do - target:data_add("cxx.modules.flags", f) - end - target:add("objectfiles", objectfile) - end - end -end 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..e12c7afed --- /dev/null +++ b/xmake/rules/c++/modules/modules_support/clang.lua @@ -0,0 +1,270 @@ +--!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"}) + +modulesflag = nil +modulestsflag = nil +implicitmodules = nil +implicitmodulemapsflag = nil +prebuiltmodulepathflag = nil + +function get_bmi_ext() + return ".pcm" +end + +-- load parent target with modules files +function load_parent(target, opt) + local common = import("common") + local cachedir = common.get_cache_dir(target) + local stlcachedir = common.get_stlcache_dir(target) + + -- add module flags + target:add("cxxflags", modulesflag or modulestsflag) + + -- add the module cache directory + target:add("cxxflags", implicitmodulesflag, {force = true}) + target:add("cxxflags", implicitmodulemapsflag, {force = true}) + + target:add("cxxflags", prebuiltmodulepathflag .. cachedir, prebuiltmodulepathflag .. stlcachedir, {force = true}) + + for _, dep in ipairs(target:orderdeps()) do + cachedir = common.get_cache_dir(dep) + target:add("cxxflags", prebuiltmodulepathflag .. cachedir, prebuiltmodulepathflag .. stlcachedir, {force = true}) + target:add("cxxflags", prebuiltmodulepathflag .. cachedir, {force = true}) + end +end + +-- check C++20 module support +function check_module_support(target) + local compinst = compiler.load("cxx", {target = target}) + + if compinst:has_flags("-fmodules", "cxxflags", {flagskey = "clang_modules"}) then + modulesflag = "-fmodules" + end + + if compinst:has_flags("-fmodules-ts", "cxxflags", {flagskey = "clang_modules_ts"}) then + modulestsflag = "-fmodules-ts" + end + assert(modulesflag or modulestsflag, "compiler(clang): does not support c++ module!") + + if compinst:has_flags((modulesflag or modulestsflag) .. " -fimplicit-modules", "cxxflags", {flagskey = "clang_implicit_modules"}) then + implicitmodulesflag = "-fimplicit-modules" + end + assert(implicitmodulesflag, "compiler(clang): does not support c++ module!") + + if compinst:has_flags((modulesflag or modulestsflag) .. " -fimplicit-module-maps" .. os.tmpdir(), "cxxflags", {flagskey = "clang_implicit_module_path"}) then + implicitmodulemapsflag = "-fimplicit-module-maps" + end + assert(implicitmodulemapsflag, "compiler(clang): does not support c++ module!") + + if compinst:has_flags((modulesflag or modulestsflag) .. " -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!") + + if compinst:has_flags((modulesflag or modulestsflag) .. " -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!") + + if compinst:has_flags((modulesflag or modulestsflag) .. " -emit-module", "cxxflags", {flagskey = "clang_emit_module"}) then + emitmoduleflag = " -emit-module" + end + assert(emitmoduleflag, "compiler(clang): does not support c++ module!") + + if compinst:has_flags((modulesflag or modulestsflag) .. " -fmodule-file=" .. os.tmpfile() .. get_bmi_ext(), "cxxflags", {flagskey = "clang_module_file"}) then + modulefileflag = "-fmodule-file=" + end + assert(modulefileflag, "compiler(clang): does not support c++ module!") + + if compinst:has_flags((modulesflag or modulestsflag) .. " -emit-module-interface", "cxxflags", {flagskey = "clang_emit_module_interface"}) then + emitmoduleinterfaceflag = "-emit-module-interface" + end + assert(emitmoduleinterfaceflag, "compiler(clang): does not support c++ module!") +end + +function toolchain_include_directories(target) + if is_plat("linux") then + return { "/usr/include/**", "/usr/local/include/**" } + end + + return {} +end + +function generate_dependencies(target, sourcebatch, opt) + local common = import("common") + local cachedir = common.get_cache_dir(target) + + for _, sourcefile in ipairs(sourcebatch.sourcefiles) do + local dependfile = target:dependfile(sourcefile) + depend.on_changed(function() + progress.show(opt.progress, "${color.build.object}generating.cxx.module.deps %s", sourcefile) + + local outdir = path.translate(path.join(cachedir, path.directory(path.relative(sourcefile, target:scriptdir())))) + if not os.isdir(outdir) then + os.mkdir(outdir) + end + + local jsonfile = path.translate(path.join(outdir, path.filename(sourcefile) .. ".json")) + + -- no support of p1689 atm + common.fallback_generate_dependencies(target, jsonfile, sourcefile) + + local dependinfo = io.readfile(jsonfile) + + return { moduleinfo = dependinfo } + end, {dependfile = dependfile, files = {sourcefile}}) + end +end + +-- generate target header units +function generate_headerunits(target, batchcmds, sourcebatch, opt) + local common = import("common") + + local compinst = target:compiler("cxx") + + local cachedir = common.get_cache_dir(target) + local stlcachedir = common.get_stlcache_dir(target) + + -- build headerunits + local objectfiles = {} + local public_flags = {} + local private_flags = {} + for _, headerunit in ipairs(sourcebatch) do + if not headerunit.stl then + local file = path.relative(headerunit.path, target:scriptdir()) + + local objectfile = target:objectfile(file) + + local outdir + if headerunit.type == ":quote" then + outdir = path.join(cachedir, path.directory(path.relative(headerunit.path, project.directory()))) + else + outdir = path.join(cachedir, path.directory(headerunit.path)) + end + + if not os.isdir(outdir) then + os.mkdir(outdir) + end + + local bmifilename = path.basename(objectfile) .. get_bmi_ext() + + local bmifile = (outdir and path.join(outdir, bmifilename) or bmifilename) + if not os.isdir(path.directory(objectfile)) then + os.mkdir(path.directory(objectfile)) + end + + local args = { modulecachepathflag .. cachedir, emitmoduleflag, "-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 + + 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) + batchcmds:set_depmtime(os.mtime(bmifile)) + batchcmds:set_depcache(target:dependfile(bmifile)) + + table.append(public_flags, modulefileflag .. bmifile) + else + local bmifile = path.join(stlcachedir, headerunit.name .. get_bmi_ext()) + + if not os.isfile(bmifile) then + local args = { modulecachepathflag .. stlcachedir, "-c", "-o", bmifile, "-x", "c++-system-header", headerunit.path } + + 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:set_depmtime(os.mtime(bmifile)) + batchcmds:set_depcache(target:dependfile(bmifile)) + + table.append(private_flags, modulefileflag .. bmifile) + end + end + + return public_flags, private_flags +end + +-- build module files +function build_modules(target, batchcmds, objectfiles, modules, opt) + local cachedir = common.get_cache_dir(target) + + local compinst = target:compiler("cxx") + + -- append deps modules + local flags = {} + for _, dep in ipairs(target:orderdeps()) do + table.join2(flags, dep:data("cxx.modules.flags")) + end + flags = table.unique(flags) + target:add("cxxflags", flags, {force = true, expand = false}) + + local common_args = { modulecachepathflag .. cachedir } + for _, objectfile in ipairs(objectfiles) do + local m = modules[objectfile] + + if m then + if not os.isdir(path.directory(objectfile)) then + os.mkdir(path.directory(objectfile)) + end + + local args = { emitmoduleinterfaceflag } + local flag = {} + local bmifiles = {} + for name, provide in pairs(m.provides) do + batchcmds:show_progress(opt.progress, "${color.build.object}generating.cxx.module.bmi %s", name) + + local bmifile = provide.bmi + table.join2(args, { "-c", "-x", "c++-module", "--precompile", provide.sourcefile, "-o", bmifile }) + table.join2(bmifiles, bmifile) + + batchcmds:add_depfiles(provide.sourcefile) + batchcmds:set_depmtime(os.mtime(bmifile)) + batchcmds:set_depcache(target:dependfile(bmifile)) + + table.join2(flag, { modulefileflag .. bmifile }) + end + + batchcmds:vrunv(compinst:program(), table.join(compinst:compflags({target = target}), common_args, args)) + batchcmds:vrunv(compinst:program(), table.join(compinst:compflags({target = target}), common_args, bmifiles, {"-c", "-o", objectfile})) + + batchcmds:set_depmtime(os.mtime(objectfile)) + batchcmds:set_depcache(target:dependfile(objectfile)) + + target:add("cxxflags", flag, {public = true, force = true}) + target:add("objectfiles", objectfile) + for _, f in ipairs(flag) do + target:data_add("cxx.modules.flags", f) + end + end + 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..0bfcc7211 --- /dev/null +++ b/xmake/rules/c++/modules/modules_support/common.lua @@ -0,0 +1,423 @@ +import("core.base.json") +import("core.project.config") +import("core.tool.compiler") +import("core.project.project") +import("lib.detect.find_file") + +local stl_headers = { + "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"} + +function get_stl_headers() + return stl_headers +end + +function is_stl_header(header) + for _, stl_header in ipairs(stl_headers) do + if stl_header == header then + return true + end + end + + return false +end + +function get_stlcache_dir(target) + local stlcachedir = path.join(target:autogendir(), "stlmodules", "cache") + if target:has_tool("cxx", "clang", "clangxx") then + stlcachedir = path.join(config.buildir(), "stlmodules", "cache") + end + if not os.isdir(stlcachedir) then + os.mkdir(stlcachedir) + end + + return path.translate(stlcachedir) +end + +function get_cache_dir(target) + local cachedir = path.join(target:autogendir(), "rules", "modules", "cache") + if not os.isdir(cachedir) then + os.mkdir(cachedir) + end + + return path.translate(cachedir) +end + +function patch_sourcebatch(target, sourcebatch, opt) + local cachedir = get_cache_dir(target) + + sourcebatch.sourcekind = "cxx" + sourcebatch.objectfiles = sourcebatch.objectfiles or {} + sourcebatch.dependfiles = sourcebatch.dependfiles or {} + + 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 + +function get_bmi_ext(target) + if target:has_tool("cxx", "gcc", "gxx") then + return import("gcc").get_bmi_ext() + elseif target:has_tool("cxx", "cl") then + return import("msvc").get_bmi_ext() + elseif target:has_tool("cxx", "clang", "clangxx") then + return import("clang").get_bmi_ext() + end + + assert(false) +end + +function load(target, sourcebatch, opt) + local cachedir = get_cache_dir(target) + + 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.append(moduleinfos, moduleinfo) + end + end + end + end + + return moduleinfos +end + +function parse_dependency_data(target, moduleinfos, opt) + local cachedir = get_cache_dir(target) + + local modules + for _, moduleinfo in ipairs(moduleinfos) do + assert(moduleinfo.version <= 1) + for _, rule in ipairs(moduleinfo.rules) do + modules = modules or {} + + local m = {} + + 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"] .. get_bmi_ext(target) + name:replace(":", "-") + + m.provides[provide["logical-name"] ] = { + bmi = path.join(cachedir, name), + sourcefile = moduleinfo.sourcefile + } + end + 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] + + assert(m1.provides) + for _, n in ipairs(nodes) do + if not n.tempmarked then + local m2 = modules[n.objectfile] + + 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 + + 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 + +function sort_modules_by_dependencies(objectfiles, modules) + local output = {} + + local nodes = {} + for _, objectfile in ipairs(objectfiles) do + local m = modules[objectfile] + + if m.provides then + table.append(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) + -- check if the header is in subtarget + + local modules_support + if target:has_tool("cxx", "clang", "clangxx") then + modules_support = import("clang") + elseif target:has_tool("cxx", "gcc", "gxx") then + modules_support = import("gcc") + elseif target:has_tool("cxx", "cl") then + modules_support = import("msvc") + else + local _, toolname = target:tool("cxx") + raise("compiler(%s): does not support c++ module!", toolname) + end + + local headerpaths = modules_support.toolchain_include_directories(target) + + for _, dep in ipairs(target:orderdeps()) do + table.append(headerpaths, dep:scriptdir()) + end + + if project.required_packages then + for _, name in ipairs(target:get("packages")) do + local package = project.required_package(name) + table.join2(headerpaths, package:get("sysincludedirs")) + end + end + + table.join2(headerpaths, target:get("includedirs")) + + local p = find_file(file, headerpaths) + + assert(p) + assert(os.isfile(p)) + + return p +end + +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*;") + + 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.append(rule.outputs, module_name .. get_bmi_ext(target)) + + local provide = {} + provide["logical-name"] = module_name + provide["source-path"] = path.absolute(sourcefile, project.directory()) + + rule.provides = {} + table.append(rule.provides, provide) + end + + rule.requires = module_deps + + table.append(output.rules, rule) + + local jsondata = json.encode(output) + + io.writefile(jsonfile, jsondata) +end \ No newline at end of file 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..5f66db54f --- /dev/null +++ b/xmake/rules/c++/modules/modules_support/gcc.lua @@ -0,0 +1,259 @@ +--!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"}) + +modulesflag = nil +modulemapperflag = nil +trtbdflag = nil + +function get_bmi_ext() + return ".gcm" +end + +-- 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 +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:printf("%s %s\n", module, bmi) + f:close() + + return true +end + +-- load parent target with modules files +function load_parent(target, opt) + local common = import("common") + local cachedir = common.get_cache_dir(target) + + target:add("cxxflags", modulesflag) + if os.isfile(get_module_mapper()) then + os.rm(get_module_mapper()) + end + target:add("cxxflags", "-fmodule-mapper=" .. get_module_mapper(), {force = true, expand = false}) +end + +-- check C++20 module support +function check_module_support(target) + local compinst = compiler.load("cxx", {target = target}) + + 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!") + + 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!") + + if compinst:has_flags("-fdep-format=trtbd", "cxxflags", {flagskey = "gcc_dep_format"}) then + trtbdflag = "-fdep-format=trtbd" + end + + if compinst:has_flags("-fdep-file=" .. os.tmpfile(), "cxxflags", {flagskey = "gcc_dep_file"}) then + depfileflag = "-fdep-file=" + end + + if compinst:has_flags("-fdep-output=" .. os.tmpfile() .. ".o", "cxxflags", {flagskey = "gcc_dep_output"}) then + depoutputflag = "-fdep-output=" + end +end + +-- provide toolchain include dir for stl headerunit when p1689 is not supported +function toolchain_include_directories(target) + if is_plat("linux") then + return { "/usr/include/**", "/usr/local/include/**" } + end + + return {} +end + +-- generate dependency files +function generate_dependencies(target, sourcebatch, opt) + local common = import("common") + local cachedir = common.get_cache_dir(target) + local compinst = target:compiler("cxx") + local common_args = {"-E", "-x", "c++"} + + for _, sourcefile in ipairs(sourcebatch.sourcefiles) do + local dependfile = target:dependfile(sourcefile) + depend.on_changed(function() + progress.show(opt.progress, "${color.build.object}generating.cxx.module.deps %s", sourcefile) + + local outdir = path.translate(path.join(cachedir, path.directory(path.relative(sourcefile, target:scriptdir())))) + if not os.isdir(outdir) then + os.mkdir(outdir) + end + + local jsonfile = path.translate(path.join(outdir, path.filename(sourcefile) .. ".json")) + + if trtbdflag and depfileflag and depoutputflag then + local ifile = path.translate(path.join(outdir, path.filename(sourcefile) .. ".i")) + local dfile = path.translate(path.join(outdir, 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 + + local dependinfo = io.readfile(jsonfile) + + return { moduleinfo = dependinfo } + end, {dependfile = dependfile, files = {sourcefile}}) + end +end + +-- generate target header units +function generate_headerunits(target, batchcmds, sourcebatch, opt) + local common = import("common") + + local compinst = target:compiler("cxx") + + local cachedir = common.get_cache_dir(target) + local stlcachedir = common.get_stlcache_dir(target) + + local mapper_file = get_module_mapper() + + -- build headerunits + local objectfiles = {} + for _, headerunit in ipairs(sourcebatch) do + if not headerunit.stl then + local file = path.relative(headerunit.path, target:scriptdir()) + + local objectfile = target:objectfile(file) + + local outdir + if headerunit.type == ":quote" then + outdir = path.join(cachedir, path.directory(path.relative(headerunit.path, project.directory()))) + else + outdir = path.join(cachedir, path.directory(headerunit.path)) + end + + if not os.isdir(outdir) then + os.mkdir(outdir) + end + + local bmifilename = path.basename(objectfile) .. get_bmi_ext() + + local bmifile = (outdir and path.join(outdir, bmifilename) or bmifilename) + if not os.isdir(path.directory(objectfile)) then + os.mkdir(path.directory(objectfile)) + end + + if add_module_to_mapper(mapper_file, headerunit.path, path.absolute(bmifile, project.directory())) then + local args = { "-c" } + if headerunit.type == ":quote" then + table.join2(args, { "-I", path.directory(headerunit.path), "-x", "c++-user-header", headerunit.name }) + add_module_to_mapper(mapper_file, path.join(".", path.relative(headerunit.path, project.directory())), path.absolute(bmifile, project.directory())) + elseif headerunit.type == ":angle" then + table.join2(args, { "-x", "c++-system-header", headerunit.name }) + add_module_to_mapper(mapper_file, headerunit.name, path.absolute(bmifile, project.directory())) + 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) + batchcmds:set_depmtime(os.mtime(bmifile)) + batchcmds:set_depcache(target:dependfile(bmifile)) + end + else + local bmifile = path.join(stlcachedir, headerunit.name .. get_bmi_ext()) + + if add_module_to_mapper(mapper_file, headerunit.path, path.absolute(bmifile, project.directory())) then + 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)) + + batchcmds:set_depmtime(os.mtime(bmifile)) + batchcmds:set_depcache(target:dependfile(bmifile)) + end + end + end + + end +end + +-- build module files +function build_modules(target, batchcmds, objectfiles, modules, opt) + local cachedir = common.get_cache_dir(target) + + local compinst = target:compiler("cxx") + + local mapper_file = get_module_mapper() + local common_args = { "-x", "c++" } + for _, objectfile in ipairs(objectfiles) do + local m = modules[objectfile] + + if m then + if not os.isdir(path.directory(objectfile)) then + os.mkdir(path.directory(objectfile)) + end + + local args = { "-o", objectfile } + for name, provide in pairs(m.provides) do + batchcmds:show_progress(opt.progress, "${color.build.object}generating.cxx.module.bmi %s", name) + + local bmifile = provide.bmi + if add_module_to_mapper(mapper_file, name, path.absolute(bmifile, project.directory())) then + table.join2(args, { "-c", provide.sourcefile }) + + batchcmds:add_depfiles(provide.sourcefile) + batchcmds:set_depmtime(os.mtime(bmifile)) + batchcmds:set_depcache(target:dependfile(bmifile)) + end + end + + batchcmds:vrunv(compinst:program(), table.join(compinst:compflags({target = target}), common_args, args)) + + batchcmds:set_depmtime(os.mtime(objectfile)) + batchcmds:set_depcache(target:dependfile(objectfile)) + + target:add("objectfiles", objectfile) + end + end +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..0a701e0f6 --- /dev/null +++ b/xmake/rules/c++/modules/modules_support/msvc.lua @@ -0,0 +1,327 @@ +--!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 msvc.lua +-- + +-- imports +import("core.tool.compiler") +import("core.project.project") +import("core.project.depend") +import("utils.progress") +import("private.action.build.object", {alias = "objectbuilder"}) + +modulesflag = nil +ifcoutputflag = nil +ifcsearchdirflag = nil +interfaceflag = nil +referenceflag = nil +headernameflag = nil +headerunitflag = nil +exportheaderflag = nil +stdifcdirflag = nil +scandependenciesflag = nil +cxxsourcefileflag = "/TP" + +function get_bmi_ext() + return ".ifc" +end + +-- load parent target with modules files +function load_parent(target, opt) + local common = import("common") + -- get modules flag + local compinst = target:compiler("cxx") + + -- add module flags + local cachedir = common.get_cache_dir(target) + local stlcachedir = common.get_stlcache_dir(target) + + target:add("cxxflags", modulesflag) + target:add("cxxflags", {ifcsearchdirflag, cachedir}, {force = true, expand = false}) + target:add("cxxflags", {ifcsearchdirflag, stlcachedir}, {force = true, expand = false}) + + for _, dep in ipairs(target:orderdeps()) do + cachedir = common.get_cache_dir(dep) + target:add("cxxflags", {ifcsearchdirflag, cachedir}, {force = true, expand = false}) + end + + 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 + +-- check C++20 module support +function check_module_support(target) + 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!") + + -- get ifcoutput flag + if compinst:has_flags("/ifcOutput", "cxxflags", {flagskey = "cl_ifc_output"}) then + ifcoutputflag = "/ifcOutput" + end + assert(ifcoutputflag, "compiler(msvc): does not support c++ module!") + + -- get ifcsearchdir flag + if compinst:has_flags("/ifcSearchDir", "cxxflags", {flagskey = "cl_ifc_search_dir"}) then + ifcsearchdirflag = "/ifcSearchDir" + end + assert(ifcsearchdirflag, "compiler(msvc): does not support c++ module!") + + -- get interface flag + if compinst:has_flags("/interface", "cxxflags", {flagskey = "cl_interface"}) then + interfaceflag = "/interface" + end + assert(interfaceflag, "compiler(msvc): does not support c++ module!") + + -- get reference flag + if compinst:has_flags("/reference", "cxxflags", {flagskey = "cl_reference"}) then + referenceflag = "/reference" + end + assert(referenceflag, "compiler(msvc): does not support c++ module!") + + -- get headername flag + 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 + assert(headernameflag, "compiler(msvc): does not support c++ module!") + + -- get headerunit flag + 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 + assert(headerunitflag, "compiler(msvc): does not support c++ module!") + + -- get exportheader flag + if compinst:has_flags(modulesflag .. " /exportHeader", "cxxflags", {flagskey = "cl_export_header"}) then + exportheaderflag = "/exportHeader" + end + assert(exportheaderflag, "compiler(msvc): does not support c++ module!") + + -- get stdifcdir flag + if compinst:has_flags("/stdIfcDir", "cxxflags", {flagskey = "cl_ifc_dir"}) then + stdifcdirflag = "/stdIfcDir" + end + assert(stdifcdirflag, "compiler(msvc): does not support c++ module!") + + -- get scandependencies flag + 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 +end + +-- provide toolchain include dir for stl headerunit when p1689 is not supported +function toolchain_include_directories(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 + + assert(false) +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 common = import("common") + local cachedir = common.get_cache_dir(target) + local common_args = {cxxsourcefileflag, scandependenciesflag} + + for _, sourcefile in ipairs(sourcebatch.sourcefiles) do + local dependfile = target:dependfile(sourcefile) + depend.on_changed(function() + progress.show(opt.progress, "${color.build.object}generating.cxx.module.deps %s", sourcefile) + + local outdir = path.join(cachedir, path.directory(path.relative(sourcefile, target:scriptdir()))) + if not os.isdir(outdir) then + os.mkdir(outdir) + end + + local jsonfile = path.join(outdir, 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 + + local dependinfo = io.readfile(jsonfile) + + return { moduleinfo = dependinfo } + end, {dependfile = dependfile, files = {sourcefile}}) + end +end + +-- generate target header units +function generate_headerunits(target, batchcmds, sourcebatch, opt) + local common = import("common") + + local compinst = target:compiler("cxx") + local toolchain = target:toolchain("msvc") + local vcvars = toolchain:config("vcvars") + + local cachedir = common.get_cache_dir(target) + local stlcachedir = common.get_stlcache_dir(target) + + -- build headerunits + local common_args = {cxxsourcefileflag, exportheaderflag, "/c"} + local objectfiles = {} + local public_flags = {} + local private_flags = {} + for _, headerunit in ipairs(sourcebatch) do + if not headerunit.stl then + local file = path.relative(headerunit.path, target:scriptdir()) + + local objectfile = target:objectfile(file) + + local outdir + if headerunit.type == ":quote" then + outdir = path.join(cachedir, path.directory(path.relative(headerunit.path, project.directory()))) + else + outdir = path.join(cachedir, path.directory(headerunit.path):sub(3)) + end + + if not os.isdir(outdir) then + os.mkdir(outdir) + end + + local bmifilename = path.basename(objectfile) .. get_bmi_ext() + + local bmifile = (outdir and path.join(outdir, bmifilename) or bmifilename) + if not os.isdir(path.directory(objectfile)) then + os.mkdir(path.directory(objectfile)) + end + + local args = {headernameflag .. headerunit.type, headerunit.path, ifcoutputflag, outdir, "/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) + batchcmds:set_depmtime(os.mtime(bmifile)) + batchcmds:set_depcache(target:dependfile(bmifile)) + batchcmds:set_depmtime(os.mtime(objectfile)) + batchcmds:set_depcache(target:dependfile(objectfile)) + + local flag = {headerunitflag .. headerunit.type, headerunit.name .. "=" .. path.relative(bmifile, cachedir)} + table.join2(public_flags, flag) + target:add("objectfiles", objectfile) + else + local bmifile = path.join(stlcachedir, headerunit.name .. get_bmi_ext()) + local args = {exportheaderflag, headernameflag .. ":angle", headerunit.name, ifcoutputflag, stlcachedir} + 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), {envs = vcvars}) + + batchcmds:set_depmtime(os.mtime(bmifile)) + batchcmds:set_depcache(target:dependfile(bmifile)) + + local flag = {headerunitflag .. ":angle", headerunit.name .. "=" .. headerunit.name .. get_bmi_ext()} + table.join2(private_flags, flag) + end + end + + return public_flags, private_flags +end + +-- build module files +function build_modules(target, batchcmds, objectfiles, modules, opt) + local cachedir = common.get_cache_dir(target) + + local compinst = target:compiler("cxx") + local toolchain = target:toolchain("msvc") + local vcvars = toolchain:config("vcvars") + + -- append deps modules + for _, dep in ipairs(target:orderdeps()) do + target:add("cxxflags", dep:data("cxx.modules.flags"), {force = true, expand = false}) + end + + -- compile module files to bmi files + local common_args = {cxxsourcefileflag} + for _, objectfile in ipairs(objectfiles) do + local m = modules[objectfile] + + if m then + if not os.isdir(path.directory(objectfile)) then + os.mkdir(path.directory(objectfile)) + end + + local args = {"/c", "/Fo" .. objectfile} + + local flag = {} + for name, provide in pairs(m.provides) do + batchcmds:show_progress(opt.progress, "${color.build.object}generating.cxx.module.bmi %s", name) + + local bmifile = provide.bmi + table.join2(args, {interfaceflag, ifcoutputflag, bmifile, provide.sourcefile}) + + batchcmds:add_depfiles(provide.sourcefile) + batchcmds:set_depmtime(os.mtime(bmifile)) + batchcmds:set_depcache(target:dependfile(bmifile)) + + flag = {"/reference", name .. "=" .. path.filename(bmifile)} + end + + batchcmds:vrunv(compinst:program(), table.join(compinst:compflags({target = target}), common_args, args, bmi_args), {envs = vcvars}) + + batchcmds:set_depmtime(os.mtime(objectfile)) + batchcmds:set_depcache(target:dependfile(objectfile)) + + target:add("cxxflags", flag, {force = true, expand = false}) + for _, f in ipairs(flag) do + target:data_add("cxx.modules.flags", f) + end + target:add("objectfiles", objectfile) + end + end +end diff --git a/xmake/rules/c++/modules/xmake.lua b/xmake/rules/c++/modules/xmake.lua index 8992e3133..655a52d38 100644 --- a/xmake/rules/c++/modules/xmake.lua +++ b/xmake/rules/c++/modules/xmake.lua @@ -29,34 +29,30 @@ rule("c++.build.modules") local target_with_modules = target:sourcebatches()["c++.build.modules"] and true or false for _, dep in ipairs(target:orderdeps()) do - local modulefiles = dep:get("modulefiles") - if modulefiles and #modulefiles > 0 then - target_with_modules = target:sourcebatches()["c++.build.modules"] and true or target_with_modules - break - end + target_with_modules = dep:sourcebatches()["c++.build.modules"] and true or target_with_modules end if target_with_modules then target:set("policy", "build.across_targets_in_parallel", false) - -- import build_modules - local build_modules + -- import modules_support + local modules_support if target:has_tool("cxx", "clang", "clangxx") then - build_modules = import("build_modules.clang") + modules_support = import("modules_support.clang") elseif target:has_tool("cxx", "gcc", "gxx") then - build_modules = import("build_modules.gcc") + modules_support = import("modules_support.gcc") elseif target:has_tool("cxx", "cl") then - build_modules = import("build_modules.msvc") + modules_support = import("modules_support.msvc") else local _, toolname = target:tool("cxx") raise("compiler(%s): does not support c++ module!", toolname) end -- check C++20 module support - build_modules.check_module_support(target) + modules_support.check_module_support(target) -- load parent - build_modules.load_parent(target, opt) + modules_support.load_parent(target, opt) target:set("cxx.has_modules", true) end @@ -64,33 +60,30 @@ rule("c++.build.modules") rule("c++.build.modules.dependencies") - set_sourcekinds("cxx") - set_extensions(".mpp", ".mxx", ".cppm", ".ixx") - before_build(function(target, opt) if not target:get("cxx.has_modules") then return end - local build_modules + local modules_support if target:has_tool("cxx", "clang", "clangxx") then - build_modules = import("build_modules.clang") + modules_support = import("modules_support.clang") elseif target:has_tool("cxx", "gcc", "gxx") then - build_modules = import("build_modules.gcc") + modules_support = import("modules_support.gcc") elseif target:has_tool("cxx", "cl") then - build_modules = import("build_modules.msvc") + modules_support = import("modules_support.msvc") else local _, toolname = target:tool("cxx") raise("compiler(%s): does not support c++ module!", toolname) end - local common = import("build_modules.common") + local common = import("modules_support.common") -- build dependency data - local batch = target:sourcebatches()["c++.build.modules.dependencies"] + local batch = target:sourcebatches()["c++.build.modules.builder"] common.patch_sourcebatch(target, batch, opt) - build_modules.generate_dependencies(target, batch, opt) + modules_support.generate_dependencies(target, batch, opt) local moduleinfos = common.load(target, batch, opt) local modules = common.parse_dependency_data(target, moduleinfos, opt) @@ -104,26 +97,25 @@ rule("c++.build.modules.builder") before_buildcmd_files(function(target, batchcmds, sourcebatch, opt) if not target:get("cxx.has_modules") then + sourcebatch.objectfiles = {} return end - local build_modules + local modules_support if target:has_tool("cxx", "clang", "clangxx") then - build_modules = import("build_modules.clang") + modules_support = import("modules_support.clang") elseif target:has_tool("cxx", "gcc", "gxx") then - build_modules = import("build_modules.gcc") + modules_support = import("modules_support.gcc") elseif target:has_tool("cxx", "cl") then - build_modules = import("build_modules.msvc") + modules_support = import("modules_support.msvc") else local _, toolname = target:tool("cxx") raise("compiler(%s): does not support c++ module!", toolname) end - local common = import("build_modules.common") + local common = import("modules_support.common") local batch = sourcebatch - print(sourcebatch) - print(batch) batch.objectfiles = {} batch.dependfiles = {} @@ -151,7 +143,7 @@ rule("c++.build.modules.builder") local headerunits_flags local private_headerunits_flags if headerunits then - headerunits_flags, private_headerunits_flags = build_modules.generate_headerunits(target, batchcmds, headerunits, opt) + headerunits_flags, private_headerunits_flags = modules_support.generate_headerunits(target, batchcmds, headerunits, opt) end if headerunits_flags then @@ -166,14 +158,16 @@ rule("c++.build.modules.builder") -- topological sort local objectfiles = common.sort_modules_by_dependencies(batch.objectfiles, modules) - build_modules.build_modules(target, batchcmds, objectfiles, modules, opt) + modules_support.build_modules(target, batchcmds, objectfiles, modules, opt) end) -rules("c++.build.modules.install") +rule("c++.build.modules.install") set_extensions(".mpp", ".mxx", ".cppm", ".ixx") - on_config(function (target) + before_install(function (target) local sourcebatch = target:sourcebatches()["c++.build.modules.install"] - target:add("installfiles", sourcebatch.sourcefiles, {prefixdir = "include"}) - end \ No newline at end of file + if sourcebatch then + target:add("installfiles", sourcebatch.sourcefiles, {prefixdir = "include"}) + end + end) \ No newline at end of file -- cgit v1.3.1 From d01b160c0e0f007ca0e8fc3c97ad47d3f79ace9c Mon Sep 17 00:00:00 2001 From: Arthur LAURENT Date: Wed, 3 Aug 2022 03:24:01 +0200 Subject: [C++20 Modules] Restore removed comments --- xmake/rules/c++/modules/xmake.lua | 2 ++ 1 file changed, 2 insertions(+) (limited to 'xmake/rules/c++/modules/xmake.lua') diff --git a/xmake/rules/c++/modules/xmake.lua b/xmake/rules/c++/modules/xmake.lua index 655a52d38..f4b2498d7 100644 --- a/xmake/rules/c++/modules/xmake.lua +++ b/xmake/rules/c++/modules/xmake.lua @@ -33,6 +33,8 @@ rule("c++.build.modules") end if target_with_modules then + -- we disable to build across targets in parallel, because the source files may depend on other target modules + -- @see https://github.com/xmake-io/xmake/issues/1858 target:set("policy", "build.across_targets_in_parallel", false) -- import modules_support -- cgit v1.3.1 From fd7340759c71bd253da2d2ccaeede55fb57dfa24 Mon Sep 17 00:00:00 2001 From: Arthur LAURENT Date: Wed, 3 Aug 2022 03:24:25 +0200 Subject: [C++20] Improve target_with_modules detection --- xmake/rules/c++/modules/xmake.lua | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) (limited to 'xmake/rules/c++/modules/xmake.lua') diff --git a/xmake/rules/c++/modules/xmake.lua b/xmake/rules/c++/modules/xmake.lua index f4b2498d7..fd178f1d8 100644 --- a/xmake/rules/c++/modules/xmake.lua +++ b/xmake/rules/c++/modules/xmake.lua @@ -26,12 +26,19 @@ rule("c++.build.modules") add_deps("c++.build.modules.install") on_config(function (target) - local target_with_modules = target:sourcebatches()["c++.build.modules"] and true or false - - for _, dep in ipairs(target:orderdeps()) do - target_with_modules = dep:sourcebatches()["c++.build.modules"] and true or target_with_modules + function contains_modules(target) + local target_with_modules + for _, dep in ipairs(target:orderdeps()) do + local sourcebatches = dep:sourcebatches() + if sourcebatches and sourcebatches["c++.build.modules"] then + target_with_modules = true + break + end + end + return target_with_modules end + local target_with_modules = target:sourcebatches()["c++.build.modules"] and contains_modules(target) or false if target_with_modules then -- we disable to build across targets in parallel, because the source files may depend on other target modules -- @see https://github.com/xmake-io/xmake/issues/1858 -- cgit v1.3.1 From da5627858bd40e911a8d5514a80613e35f5716e1 Mon Sep 17 00:00:00 2001 From: Arthur LAURENT Date: Wed, 3 Aug 2022 03:28:35 +0200 Subject: [C++20 Modules] Use a helper function to get module_builder --- xmake/rules/c++/modules/modules_support/common.lua | 15 ++++++++ xmake/rules/c++/modules/xmake.lua | 40 +++------------------- 2 files changed, 19 insertions(+), 36 deletions(-) (limited to 'xmake/rules/c++/modules/xmake.lua') diff --git a/xmake/rules/c++/modules/modules_support/common.lua b/xmake/rules/c++/modules/modules_support/common.lua index 0bfcc7211..7df0b3d34 100644 --- a/xmake/rules/c++/modules/modules_support/common.lua +++ b/xmake/rules/c++/modules/modules_support/common.lua @@ -148,6 +148,21 @@ function get_bmi_ext(target) assert(false) end +function modules_support(target) + local module_builder + 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 + return module_builder +end + function load(target, sourcebatch, opt) local cachedir = get_cache_dir(target) diff --git a/xmake/rules/c++/modules/xmake.lua b/xmake/rules/c++/modules/xmake.lua index fd178f1d8..233fcab9f 100644 --- a/xmake/rules/c++/modules/xmake.lua +++ b/xmake/rules/c++/modules/xmake.lua @@ -44,18 +44,8 @@ rule("c++.build.modules") -- @see https://github.com/xmake-io/xmake/issues/1858 target:set("policy", "build.across_targets_in_parallel", false) - -- import modules_support - local modules_support - if target:has_tool("cxx", "clang", "clangxx") then - modules_support = import("modules_support.clang") - elseif target:has_tool("cxx", "gcc", "gxx") then - modules_support = import("modules_support.gcc") - elseif target:has_tool("cxx", "cl") then - modules_support = import("modules_support.msvc") - else - local _, toolname = target:tool("cxx") - raise("compiler(%s): does not support c++ module!", toolname) - end + local common = import("modules_support.common") + local modules_support = common.modules_support(target) -- check C++20 module support modules_support.check_module_support(target) @@ -74,19 +64,8 @@ rule("c++.build.modules.dependencies") return end - local modules_support - if target:has_tool("cxx", "clang", "clangxx") then - modules_support = import("modules_support.clang") - elseif target:has_tool("cxx", "gcc", "gxx") then - modules_support = import("modules_support.gcc") - elseif target:has_tool("cxx", "cl") then - modules_support = import("modules_support.msvc") - else - local _, toolname = target:tool("cxx") - raise("compiler(%s): does not support c++ module!", toolname) - end - local common = import("modules_support.common") + local modules_support = common.modules_support(target) -- build dependency data local batch = target:sourcebatches()["c++.build.modules.builder"] @@ -110,19 +89,8 @@ rule("c++.build.modules.builder") return end - local modules_support - if target:has_tool("cxx", "clang", "clangxx") then - modules_support = import("modules_support.clang") - elseif target:has_tool("cxx", "gcc", "gxx") then - modules_support = import("modules_support.gcc") - elseif target:has_tool("cxx", "cl") then - modules_support = import("modules_support.msvc") - else - local _, toolname = target:tool("cxx") - raise("compiler(%s): does not support c++ module!", toolname) - end - local common = import("modules_support.common") + local modules_support = common.modules_support(target) local batch = sourcebatch -- cgit v1.3.1 From e40e8ba4cb7c7b83fa9ef1a5fbdffe7708a8de02 Mon Sep 17 00:00:00 2001 From: Arthur LAURENT Date: Wed, 3 Aug 2022 03:30:03 +0200 Subject: [C++20 Modules] Restore removed comments --- xmake/rules/c++/modules/xmake.lua | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'xmake/rules/c++/modules/xmake.lua') diff --git a/xmake/rules/c++/modules/xmake.lua b/xmake/rules/c++/modules/xmake.lua index 233fcab9f..be87a7eb0 100644 --- a/xmake/rules/c++/modules/xmake.lua +++ b/xmake/rules/c++/modules/xmake.lua @@ -26,6 +26,9 @@ rule("c++.build.modules") add_deps("c++.build.modules.install") on_config(function (target) + -- we disable to build across targets in parallel, because the source files may depend on other target modules + -- @see https://github.com/xmake-io/xmake/issues/1858 + function contains_modules(target) local target_with_modules for _, dep in ipairs(target:orderdeps()) do @@ -40,8 +43,10 @@ rule("c++.build.modules") local target_with_modules = target:sourcebatches()["c++.build.modules"] and contains_modules(target) or false if target_with_modules then - -- we disable to build across targets in parallel, because the source files may depend on other target modules - -- @see https://github.com/xmake-io/xmake/issues/1858 + -- @note this will cause cross-parallel builds to be disabled for all sub-dependent targets, + -- even if some sub-targets do not contain C++ modules. + -- + -- maybe we will have a more fine-grained configuration strategy to disable it in the future. target:set("policy", "build.across_targets_in_parallel", false) local common = import("modules_support.common") -- cgit v1.3.1 From a9ed54fd3af64a04bb7d617a51dde689d23c80de Mon Sep 17 00:00:00 2001 From: Arthur LAURENT Date: Wed, 3 Aug 2022 03:32:16 +0200 Subject: [C++20 Modules] Move contains_modules function into common.lua --- xmake/rules/c++/modules/modules_support/common.lua | 12 ++++++++++++ xmake/rules/c++/modules/xmake.lua | 17 ++--------------- 2 files changed, 14 insertions(+), 15 deletions(-) (limited to 'xmake/rules/c++/modules/xmake.lua') diff --git a/xmake/rules/c++/modules/modules_support/common.lua b/xmake/rules/c++/modules/modules_support/common.lua index 7df0b3d34..ca637cbf2 100644 --- a/xmake/rules/c++/modules/modules_support/common.lua +++ b/xmake/rules/c++/modules/modules_support/common.lua @@ -163,6 +163,18 @@ function modules_support(target) return module_builder end +function contains_modules(target) + local target_with_modules + for _, dep in ipairs(target:orderdeps()) do + local sourcebatches = dep:sourcebatches() + if sourcebatches and sourcebatches["c++.build.modules"] then + target_with_modules = true + break + end + end + return target_with_modules +end + function load(target, sourcebatch, opt) local cachedir = get_cache_dir(target) diff --git a/xmake/rules/c++/modules/xmake.lua b/xmake/rules/c++/modules/xmake.lua index be87a7eb0..436d7c5e2 100644 --- a/xmake/rules/c++/modules/xmake.lua +++ b/xmake/rules/c++/modules/xmake.lua @@ -28,20 +28,9 @@ rule("c++.build.modules") on_config(function (target) -- we disable to build across targets in parallel, because the source files may depend on other target modules -- @see https://github.com/xmake-io/xmake/issues/1858 + local common = import("modules_support.common") - function contains_modules(target) - local target_with_modules - for _, dep in ipairs(target:orderdeps()) do - local sourcebatches = dep:sourcebatches() - if sourcebatches and sourcebatches["c++.build.modules"] then - target_with_modules = true - break - end - end - return target_with_modules - end - - local target_with_modules = target:sourcebatches()["c++.build.modules"] and contains_modules(target) or false + local target_with_modules = target:sourcebatches()["c++.build.modules"] and common.contains_modules(target) or false if target_with_modules then -- @note this will cause cross-parallel builds to be disabled for all sub-dependent targets, -- even if some sub-targets do not contain C++ modules. @@ -49,7 +38,6 @@ rule("c++.build.modules") -- maybe we will have a more fine-grained configuration strategy to disable it in the future. target:set("policy", "build.across_targets_in_parallel", false) - local common = import("modules_support.common") local modules_support = common.modules_support(target) -- check C++20 module support @@ -148,7 +136,6 @@ rule("c++.build.modules.install") before_install(function (target) local sourcebatch = target:sourcebatches()["c++.build.modules.install"] - if sourcebatch then target:add("installfiles", sourcebatch.sourcefiles, {prefixdir = "include"}) end -- cgit v1.3.1 From 728126bd7816bbd5fb6b93cedc7163a425343fba Mon Sep 17 00:00:00 2001 From: Arthur LAURENT Date: Wed, 3 Aug 2022 04:10:04 +0200 Subject: [C++20 Modules] Improve support --- xmake/rules/c++/modules/modules_support/clang.lua | 260 ++++++++++++------ xmake/rules/c++/modules/modules_support/common.lua | 23 +- xmake/rules/c++/modules/modules_support/gcc.lua | 145 ++++++---- xmake/rules/c++/modules/modules_support/msvc.lua | 292 +++++++++++++-------- xmake/rules/c++/modules/xmake.lua | 25 +- 5 files changed, 490 insertions(+), 255 deletions(-) (limited to 'xmake/rules/c++/modules/xmake.lua') diff --git a/xmake/rules/c++/modules/modules_support/clang.lua b/xmake/rules/c++/modules/modules_support/clang.lua index e12c7afed..49dbee0a2 100644 --- a/xmake/rules/c++/modules/modules_support/clang.lua +++ b/xmake/rules/c++/modules/modules_support/clang.lua @@ -25,35 +25,16 @@ import("core.project.depend") import("core.project.config") import("utils.progress") import("private.action.build.object", {alias = "objectbuilder"}) - -modulesflag = nil -modulestsflag = nil -implicitmodules = nil -implicitmodulemapsflag = nil -prebuiltmodulepathflag = nil - -function get_bmi_ext() - return ".pcm" -end +import("common") -- load parent target with modules files function load_parent(target, opt) - local common = import("common") local cachedir = common.get_cache_dir(target) - local stlcachedir = common.get_stlcache_dir(target) - -- add module flags - target:add("cxxflags", modulesflag or modulestsflag) - - -- add the module cache directory - target:add("cxxflags", implicitmodulesflag, {force = true}) - target:add("cxxflags", implicitmodulemapsflag, {force = true}) - - target:add("cxxflags", prebuiltmodulepathflag .. cachedir, prebuiltmodulepathflag .. stlcachedir, {force = true}) + local prebuiltmodulepathflag = get_prebuiltmodulepathflag(target) for _, dep in ipairs(target:orderdeps()) do cachedir = common.get_cache_dir(dep) - target:add("cxxflags", prebuiltmodulepathflag .. cachedir, prebuiltmodulepathflag .. stlcachedir, {force = true}) target:add("cxxflags", prebuiltmodulepathflag .. cachedir, {force = true}) end end @@ -61,68 +42,57 @@ end -- check C++20 module support function check_module_support(target) local compinst = compiler.load("cxx", {target = target}) + local cachedir = common.get_cache_dir(target) + local stlcachedir = common.get_stlcache_dir(target) - if compinst:has_flags("-fmodules", "cxxflags", {flagskey = "clang_modules"}) then - modulesflag = "-fmodules" - end - - if compinst:has_flags("-fmodules-ts", "cxxflags", {flagskey = "clang_modules_ts"}) then - modulestsflag = "-fmodules-ts" - end - assert(modulesflag or modulestsflag, "compiler(clang): does not support c++ module!") - - if compinst:has_flags((modulesflag or modulestsflag) .. " -fimplicit-modules", "cxxflags", {flagskey = "clang_implicit_modules"}) then - implicitmodulesflag = "-fimplicit-modules" - end - assert(implicitmodulesflag, "compiler(clang): does not support c++ module!") - - if compinst:has_flags((modulesflag or modulestsflag) .. " -fimplicit-module-maps" .. os.tmpdir(), "cxxflags", {flagskey = "clang_implicit_module_path"}) then - implicitmodulemapsflag = "-fimplicit-module-maps" - end - assert(implicitmodulemapsflag, "compiler(clang): does not support c++ module!") - - if compinst:has_flags((modulesflag or modulestsflag) .. " -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!") + -- get module and module cache flags + local modulesflag = get_modulesflag(target) + local implicitmodulesflag = get_implicitmodulesflag(target) + local implicitmodulemapsflag = get_implicitmodulemapsflag(target) + local prebuiltmodulepathflag = get_prebuiltmodulepathflag(target) - if compinst:has_flags((modulesflag or modulestsflag) .. " -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!") - - if compinst:has_flags((modulesflag or modulestsflag) .. " -emit-module", "cxxflags", {flagskey = "clang_emit_module"}) then - emitmoduleflag = " -emit-module" - end - assert(emitmoduleflag, "compiler(clang): does not support c++ module!") + -- add module flags + target:add("cxxflags", modulesflag) - if compinst:has_flags((modulesflag or modulestsflag) .. " -fmodule-file=" .. os.tmpfile() .. get_bmi_ext(), "cxxflags", {flagskey = "clang_module_file"}) then - modulefileflag = "-fmodule-file=" - end - assert(modulefileflag, "compiler(clang): does not support c++ module!") + -- add the module cache directory + target:add("cxxflags", implicitmodulesflag, {force = true}) + target:add("cxxflags", implicitmodulemapsflag, {force = true}) - if compinst:has_flags((modulesflag or modulestsflag) .. " -emit-module-interface", "cxxflags", {flagskey = "clang_emit_module_interface"}) then - emitmoduleinterfaceflag = "-emit-module-interface" - end - assert(emitmoduleinterfaceflag, "compiler(clang): does not support c++ module!") + target:add("cxxflags", prebuiltmodulepathflag .. cachedir, prebuiltmodulepathflag .. stlcachedir, {force = true}) end function toolchain_include_directories(target) - if is_plat("linux") then - return { "/usr/include/**", "/usr/local/include/**" } - end + local includedirs = _g.includedirs + if includedirs == nil then + includedirs = {} - return {} + local gcc, toolname = target:tool("cc") + assert(toolname, "clang") + + 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.append(includedirs, line) + break + elseif line:startswith("End") then + break + end + end + end + _g.includedirs = includedirs or {} + end + return includedirs end function generate_dependencies(target, sourcebatch, opt) - local common = import("common") local cachedir = common.get_cache_dir(target) for _, sourcefile in ipairs(sourcebatch.sourcefiles) do local dependfile = target:dependfile(sourcefile) depend.on_changed(function() - progress.show(opt.progress, "${color.build.object}generating.cxx.module.deps %s", sourcefile) + progress.show(opt.progress, "${color.build.object}generating.cxx.module.deps %s", sourcefile) local outdir = path.translate(path.join(cachedir, path.directory(path.relative(sourcefile, target:scriptdir())))) if not os.isdir(outdir) then @@ -143,13 +113,17 @@ end -- generate target header units function generate_headerunits(target, batchcmds, sourcebatch, opt) - local common = import("common") - local compinst = target:compiler("cxx") - local cachedir = common.get_cache_dir(target) local stlcachedir = common.get_stlcache_dir(target) + assert(has_headerunitsupport(target), "compiler(clang): does not support c++ header units!") + + -- get headerunits flags + local modulecachepathflag = get_modulecachepathflag(target) + local emitmoduleflag = get_emitmoduleflag(target) + local modulefileflag = get_modulefileflag(target) + -- build headerunits local objectfiles = {} local public_flags = {} @@ -210,15 +184,18 @@ function generate_headerunits(target, batchcmds, sourcebatch, opt) table.append(private_flags, modulefileflag .. bmifile) end end - return public_flags, private_flags end -- build module files function build_modules(target, batchcmds, objectfiles, modules, opt) - local cachedir = common.get_cache_dir(target) - local compinst = target:compiler("cxx") + local cachedir = common.get_cache_dir(target) + + -- get modules flags + local modulecachepathflag = get_modulecachepathflag(target) + local emitmoduleinterfaceflag = get_emitmoduleinterfaceflag(target) + local modulefileflag = get_modulefileflag(target) -- append deps modules local flags = {} @@ -268,3 +245,140 @@ function build_modules(target, batchcmds, objectfiles, modules, opt) end end end + +function get_bmi_ext() + 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 +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 +end + +function get_implicitmodulemapsflag(target) + local implicitmodulemapsflag = _g.implicitmodulemapsflag + if implicitmodulemapsflag == nil then + local compinst = target:compiler("cxx") + if compinst:has_flags("-fimplicit-module-maps", "cxxflags", {flagskey = "clang_implicit_module_maps"}) then + implicitmodulemapsflag = "-fimplicit-module-maps" + end + assert(implicitmodulemapsflag, "compiler(clang): does not support c++ module!") + + _g.implicitmodulemapsflag = implicitmodulemapsflag or false + end + return implicitmodulemapsflag +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 +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 +end + +function get_emitmoduleflag(target) + local emitmoduleflag = _g.emitmoduleflag + if emitmoduleflag == nil then + local compinst = target:compiler("cxx") + if compinst:has_flags("-emit-module", "cxxflags", {flagskey = "clang_emit_module"}) then + emitmoduleflag = " -emit-module" + end + assert(emitmoduleflag, "compiler(clang): does not support c++ module!") + + _g.emitmoduleflag = emitmoduleflag or false + end + return emitmoduleflag +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_ext(), "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 +end + +function get_emitmoduleinterfaceflag(target) + local emitmoduleinterfaceflag = _g.emitmoduleinterfaceflag + if emitmoduleinterfaceflag == nil then + local compinst = target:compiler("cxx") + if compinst:has_flags("-emit-module-interface", "cxxflags", {flagskey = "clang_emit_module_interface"}) then + emitmoduleinterfaceflag = "-emit-module-interface" + end + assert(emitmoduleinterfaceflag, "compiler(clang): does not support c++ module!") + + _g.emitmoduleinterfaceflag = emitmoduleinterfaceflag or false + end + return emitmoduleinterfaceflag +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("-x c++-user-header", "cxxflags", {flagskey = "clang_user_header_unit_support"}) and + compinst:has_flags("-x c++-system-header", "cxxflags", {flagskey = "clang_system_header_unit_support"}) then + support_headerunits = true + end + + _g.support_headerunits = support_headerunits or false + end + return support_headerunits +end \ No newline at end of file diff --git a/xmake/rules/c++/modules/modules_support/common.lua b/xmake/rules/c++/modules/modules_support/common.lua index ca637cbf2..e706f4e81 100644 --- a/xmake/rules/c++/modules/modules_support/common.lua +++ b/xmake/rules/c++/modules/modules_support/common.lua @@ -1,10 +1,11 @@ import("core.base.json") +import("core.base.hashset") import("core.project.config") import("core.tool.compiler") import("core.project.project") import("lib.detect.find_file") -local stl_headers = { +_g.stl_headers = { "algorithm", "forward_list", "numbers", @@ -86,17 +87,11 @@ local stl_headers = { "stdexcept"} function get_stl_headers() - return stl_headers + return hashset.from(_g.stl_headers) end function is_stl_header(header) - for _, stl_header in ipairs(stl_headers) do - if stl_header == header then - return true - end - end - - return false + return get_stl_headers():has(header) end function get_stlcache_dir(target) @@ -107,7 +102,6 @@ function get_stlcache_dir(target) if not os.isdir(stlcachedir) then os.mkdir(stlcachedir) end - return path.translate(stlcachedir) end @@ -116,7 +110,6 @@ function get_cache_dir(target) if not os.isdir(cachedir) then os.mkdir(cachedir) end - return path.translate(cachedir) end @@ -133,7 +126,6 @@ function patch_sourcebatch(target, sourcebatch, opt) table.insert(sourcebatch.objectfiles, objectfile) table.insert(sourcebatch.dependfiles, dependfile) end - end function get_bmi_ext(target) @@ -144,7 +136,6 @@ function get_bmi_ext(target) elseif target:has_tool("cxx", "clang", "clangxx") then return import("clang").get_bmi_ext() end - assert(false) end @@ -164,7 +155,8 @@ function modules_support(target) end function contains_modules(target) - local target_with_modules + local target_with_modules = target:sourcebatches()["c++.build.modules"] and true or false + for _, dep in ipairs(target:orderdeps()) do local sourcebatches = dep:sourcebatches() if sourcebatches and sourcebatches["c++.build.modules"] then @@ -195,7 +187,6 @@ function load(target, sourcebatch, opt) end end end - return moduleinfos end @@ -260,7 +251,6 @@ function parse_dependency_data(target, moduleinfos, opt) end end end - return modules end @@ -329,7 +319,6 @@ function sort_modules_by_dependencies(objectfiles, modules) _topological_sort_visit(node, nodes, modules, output) end - return output end diff --git a/xmake/rules/c++/modules/modules_support/gcc.lua b/xmake/rules/c++/modules/modules_support/gcc.lua index 5f66db54f..0c8027b6e 100644 --- a/xmake/rules/c++/modules/modules_support/gcc.lua +++ b/xmake/rules/c++/modules/modules_support/gcc.lua @@ -25,14 +25,7 @@ import("core.project.depend") import("core.project.config") import("utils.progress") import("private.action.build.object", {alias = "objectbuilder"}) - -modulesflag = nil -modulemapperflag = nil -trtbdflag = nil - -function get_bmi_ext() - return ".gcm" -end +import("common") -- get and create the path of module mapper function get_module_mapper() @@ -53,7 +46,7 @@ function add_module_to_mapper(file, module, bmi) end local f = io.open(file, "a") - f:printf("%s %s\n", module, bmi) + f:print("%s %s", module, bmi) f:close() return true @@ -61,63 +54,63 @@ end -- load parent target with modules files function load_parent(target, opt) - local common = import("common") - local cachedir = common.get_cache_dir(target) - - target:add("cxxflags", modulesflag) - if os.isfile(get_module_mapper()) then - os.rm(get_module_mapper()) - end - target:add("cxxflags", "-fmodule-mapper=" .. get_module_mapper(), {force = true, expand = false}) end -- check C++20 module support function check_module_support(target) local compinst = compiler.load("cxx", {target = target}) - 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!") + local modulesflag = get_modulesflag(target) + local modulemapperflag = get_modulemapperflag(target) - 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!") - - if compinst:has_flags("-fdep-format=trtbd", "cxxflags", {flagskey = "gcc_dep_format"}) then - trtbdflag = "-fdep-format=trtbd" - end - - if compinst:has_flags("-fdep-file=" .. os.tmpfile(), "cxxflags", {flagskey = "gcc_dep_file"}) then - depfileflag = "-fdep-file=" - end + target:add("cxxflags", modulesflag) - if compinst:has_flags("-fdep-output=" .. os.tmpfile() .. ".o", "cxxflags", {flagskey = "gcc_dep_output"}) then - depoutputflag = "-fdep-output=" + 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 -- provide toolchain include dir for stl headerunit when p1689 is not supported function toolchain_include_directories(target) - if is_plat("linux") then - return { "/usr/include/**", "/usr/local/include/**" } - end - - return {} + local includedirs = _g.includedirs + if includedirs == nil then + includedirs = {} + + local gcc, toolname = target:tool("cc") + assert(toolname, "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.append(includedirs, line) + break + elseif line:startswith("End") then + break + end + end + end + _g.includedirs = includedirs or {} + end + return includedirs end -- generate dependency files function generate_dependencies(target, sourcebatch, opt) - local common = import("common") local cachedir = common.get_cache_dir(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) + for _, sourcefile in ipairs(sourcebatch.sourcefiles) do local dependfile = target:dependfile(sourcefile) depend.on_changed(function() - progress.show(opt.progress, "${color.build.object}generating.cxx.module.deps %s", sourcefile) + progress.show(opt.progress, "${color.build.object}generating.cxx.module.deps %s", sourcefile) local outdir = path.translate(path.join(cachedir, path.directory(path.relative(sourcefile, target:scriptdir())))) if not os.isdir(outdir) then @@ -146,8 +139,6 @@ end -- generate target header units function generate_headerunits(target, batchcmds, sourcebatch, opt) - local common = import("common") - local compinst = target:compiler("cxx") local cachedir = common.get_cache_dir(target) @@ -213,7 +204,6 @@ function generate_headerunits(target, batchcmds, sourcebatch, opt) end end end - end end @@ -257,3 +247,68 @@ function build_modules(target, batchcmds, objectfiles, modules, opt) end end +function get_bmi_ext() + 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 +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 +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 +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 +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 +end diff --git a/xmake/rules/c++/modules/modules_support/msvc.lua b/xmake/rules/c++/modules/modules_support/msvc.lua index 0a701e0f6..1851d678c 100644 --- a/xmake/rules/c++/modules/modules_support/msvc.lua +++ b/xmake/rules/c++/modules/modules_support/msvc.lua @@ -24,42 +24,35 @@ import("core.project.project") import("core.project.depend") import("utils.progress") import("private.action.build.object", {alias = "objectbuilder"}) +import("common") -modulesflag = nil -ifcoutputflag = nil -ifcsearchdirflag = nil -interfaceflag = nil -referenceflag = nil -headernameflag = nil -headerunitflag = nil -exportheaderflag = nil -stdifcdirflag = nil -scandependenciesflag = nil -cxxsourcefileflag = "/TP" +-- load parent target with modules files +function load_parent(target, opt) + local ifcsearchdirflag = get_ifcsearchdirflag(target) -function get_bmi_ext() - return ".ifc" + for _, dep in ipairs(target:orderdeps()) do + cachedir = common.get_cache_dir(dep) + target:add("cxxflags", {ifcsearchdirflag, cachedir}, {force = true, expand = false}) + end end --- load parent target with modules files -function load_parent(target, opt) - local common = import("common") - -- get modules flag +-- check C++20 module support +function check_module_support(target) local compinst = target:compiler("cxx") - - -- add module flags local cachedir = common.get_cache_dir(target) local stlcachedir = common.get_stlcache_dir(target) + -- get flags + local modulesflag = get_modulesflag(target) + local ifcsearchdirflag = get_ifcsearchdirflag(target) + + -- add modules flags target:add("cxxflags", modulesflag) target:add("cxxflags", {ifcsearchdirflag, cachedir}, {force = true, expand = false}) target:add("cxxflags", {ifcsearchdirflag, stlcachedir}, {force = true, expand = false}) - for _, dep in ipairs(target:orderdeps()) do - cachedir = common.get_cache_dir(dep) - target:add("cxxflags", {ifcsearchdirflag, cachedir}, {force = true, expand = false}) - end - + -- add stdifcdir in case of if the user want to use Microsoft modularised STL + 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") @@ -74,83 +67,6 @@ function load_parent(target, opt) end end --- check C++20 module support -function check_module_support(target) - 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!") - - -- get ifcoutput flag - if compinst:has_flags("/ifcOutput", "cxxflags", {flagskey = "cl_ifc_output"}) then - ifcoutputflag = "/ifcOutput" - end - assert(ifcoutputflag, "compiler(msvc): does not support c++ module!") - - -- get ifcsearchdir flag - if compinst:has_flags("/ifcSearchDir", "cxxflags", {flagskey = "cl_ifc_search_dir"}) then - ifcsearchdirflag = "/ifcSearchDir" - end - assert(ifcsearchdirflag, "compiler(msvc): does not support c++ module!") - - -- get interface flag - if compinst:has_flags("/interface", "cxxflags", {flagskey = "cl_interface"}) then - interfaceflag = "/interface" - end - assert(interfaceflag, "compiler(msvc): does not support c++ module!") - - -- get reference flag - if compinst:has_flags("/reference", "cxxflags", {flagskey = "cl_reference"}) then - referenceflag = "/reference" - end - assert(referenceflag, "compiler(msvc): does not support c++ module!") - - -- get headername flag - 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 - assert(headernameflag, "compiler(msvc): does not support c++ module!") - - -- get headerunit flag - 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 - assert(headerunitflag, "compiler(msvc): does not support c++ module!") - - -- get exportheader flag - if compinst:has_flags(modulesflag .. " /exportHeader", "cxxflags", {flagskey = "cl_export_header"}) then - exportheaderflag = "/exportHeader" - end - assert(exportheaderflag, "compiler(msvc): does not support c++ module!") - - -- get stdifcdir flag - if compinst:has_flags("/stdIfcDir", "cxxflags", {flagskey = "cl_ifc_dir"}) then - stdifcdirflag = "/stdIfcDir" - end - assert(stdifcdirflag, "compiler(msvc): does not support c++ module!") - - -- get scandependencies flag - 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 -end - -- provide toolchain include dir for stl headerunit when p1689 is not supported function toolchain_include_directories(target) for _, toolchain_inst in ipairs(target:toolchains()) do @@ -162,7 +78,6 @@ function toolchain_include_directories(target) break end end - assert(false) end @@ -172,13 +87,14 @@ function generate_dependencies(target, sourcebatch, opt) local toolchain = target:toolchain("msvc") local vcvars = toolchain:config("vcvars") - local common = import("common") + local scandependenciesflag = get_scandependenciesflag(target) + local cachedir = common.get_cache_dir(target) - local common_args = {cxxsourcefileflag, scandependenciesflag} + local common_args = {"/TP", scandependenciesflag} for _, sourcefile in ipairs(sourcebatch.sourcefiles) do local dependfile = target:dependfile(sourcefile) - depend.on_changed(function() + depend.on_changed(function () progress.show(opt.progress, "${color.build.object}generating.cxx.module.deps %s", sourcefile) local outdir = path.join(cachedir, path.directory(path.relative(sourcefile, target:scriptdir()))) @@ -205,17 +121,23 @@ end -- generate target header units function generate_headerunits(target, batchcmds, sourcebatch, opt) - local common = import("common") - local compinst = target:compiler("cxx") local toolchain = target:toolchain("msvc") local vcvars = toolchain:config("vcvars") + -- 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!") + local cachedir = common.get_cache_dir(target) local stlcachedir = common.get_stlcache_dir(target) -- build headerunits - local common_args = {cxxsourcefileflag, exportheaderflag, "/c"} + local common_args = {"/TP", exportheaderflag, "/c"} local objectfiles = {} local public_flags = {} local private_flags = {} @@ -281,13 +203,18 @@ function build_modules(target, batchcmds, objectfiles, modules, opt) 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) + -- append deps modules for _, dep in ipairs(target:orderdeps()) do target:add("cxxflags", dep:data("cxx.modules.flags"), {force = true, expand = false}) end -- compile module files to bmi files - local common_args = {cxxsourcefileflag} + local common_args = {"/TP"} for _, objectfile in ipairs(objectfiles) do local m = modules[objectfile] @@ -309,7 +236,7 @@ function build_modules(target, batchcmds, objectfiles, modules, opt) batchcmds:set_depmtime(os.mtime(bmifile)) batchcmds:set_depcache(target:dependfile(bmifile)) - flag = {"/reference", name .. "=" .. path.filename(bmifile)} + flag = {referenceflag, name .. "=" .. path.filename(bmifile)} end batchcmds:vrunv(compinst:program(), table.join(compinst:compflags({target = target}), common_args, args, bmi_args), {envs = vcvars}) @@ -325,3 +252,148 @@ function build_modules(target, batchcmds, objectfiles, modules, opt) end end end + +function get_bmi_ext() + 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 +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 +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 +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 +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 +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 +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 +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 +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 +end diff --git a/xmake/rules/c++/modules/xmake.lua b/xmake/rules/c++/modules/xmake.lua index 436d7c5e2..313d932f9 100644 --- a/xmake/rules/c++/modules/xmake.lua +++ b/xmake/rules/c++/modules/xmake.lua @@ -26,38 +26,39 @@ rule("c++.build.modules") add_deps("c++.build.modules.install") on_config(function (target) + import("modules_support.common") + -- we disable to build across targets in parallel, because the source files may depend on other target modules -- @see https://github.com/xmake-io/xmake/issues/1858 - local common = import("modules_support.common") - - local target_with_modules = target:sourcebatches()["c++.build.modules"] and common.contains_modules(target) or false - if target_with_modules then + if common.contains_modules(target) then -- @note this will cause cross-parallel builds to be disabled for all sub-dependent targets, -- even if some sub-targets do not contain C++ modules. -- -- maybe we will have a more fine-grained configuration strategy to disable it in the future. target:set("policy", "build.across_targets_in_parallel", false) + -- get modules support local modules_support = common.modules_support(target) -- check C++20 module support modules_support.check_module_support(target) + -- mark this target with modules + target:data_set("cxx.has_modules", true) + -- load parent modules_support.load_parent(target, opt) - - target:set("cxx.has_modules", true) end end) rule("c++.build.modules.dependencies") before_build(function(target, opt) - if not target:get("cxx.has_modules") then + if not target:data("cxx.has_modules") then return end - local common = import("modules_support.common") + import("modules_support.common") local modules_support = common.modules_support(target) -- build dependency data @@ -77,12 +78,12 @@ rule("c++.build.modules.builder") set_extensions(".mpp", ".mxx", ".cppm", ".ixx") before_buildcmd_files(function(target, batchcmds, sourcebatch, opt) - if not target:get("cxx.has_modules") then + if not target:data("cxx.has_modules") then sourcebatch.objectfiles = {} return end - local common = import("modules_support.common") + import("modules_support.common") local modules_support = common.modules_support(target) local batch = sourcebatch @@ -135,6 +136,10 @@ rule("c++.build.modules.install") set_extensions(".mpp", ".mxx", ".cppm", ".ixx") before_install(function (target) + if not target:data("cxx.has_modules") then + return + end + local sourcebatch = target:sourcebatches()["c++.build.modules.install"] if sourcebatch then target:add("installfiles", sourcebatch.sourcefiles, {prefixdir = "include"}) -- cgit v1.3.1