summaryrefslogtreecommitdiff
path: root/xmake/rules/c++/modules
diff options
context:
space:
mode:
authorruki <[email protected]>2022-08-03 17:20:55 +0800
committerGitHub <[email protected]>2022-08-03 17:20:55 +0800
commitd1de4d49e4ae33ea5364bc996a64622fa59af9eb (patch)
treeff6903a74a1151035253f53fa8cb805f10eb817f /xmake/rules/c++/modules
parent67a3575689b9761677b48bfb4588712f117924fb (diff)
parent728126bd7816bbd5fb6b93cedc7163a425343fba (diff)
Merge pull request #2623 from Arthapz/cxx20-modules-support
[Draft] Improve C++20 modules support
Diffstat (limited to 'xmake/rules/c++/modules')
-rw-r--r--xmake/rules/c++/modules/build_modules/clang.lua128
-rw-r--r--xmake/rules/c++/modules/build_modules/gcc.lua84
-rw-r--r--xmake/rules/c++/modules/build_modules/module_parser.lua149
-rw-r--r--xmake/rules/c++/modules/build_modules/msvc.lua178
-rw-r--r--xmake/rules/c++/modules/modules_support/clang.lua384
-rw-r--r--xmake/rules/c++/modules/modules_support/common.lua439
-rw-r--r--xmake/rules/c++/modules/modules_support/gcc.lua314
-rw-r--r--xmake/rules/c++/modules/modules_support/msvc.lua399
-rw-r--r--xmake/rules/c++/modules/xmake.lua140
9 files changed, 1647 insertions, 568 deletions
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 120e1d5d0..000000000
--- a/xmake/rules/c++/modules/build_modules/clang.lua
+++ /dev/null
@@ -1,128 +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.config")
-import("private.action.build.object", {alias = "objectbuilder"})
-import("module_parser")
-
--- load parent target with modules files
-function load_parent(target, opt)
- -- get modules flag
- local modulesflag
- local compinst = compiler.load("cxx", {target = target})
- if compinst:has_flags("-fmodules") then
- modulesflag = "-fmodules"
- elseif compinst:has_flags("-fmodules-ts") then
- modulesflag = "-fmodules-ts"
- end
- assert(modulesflag, "compiler(clang): does not support c++ module!")
-
- -- add module flags
- target:add("cxxflags", modulesflag)
-
- -- add the module cache directory
- local cachedir = path.join(config.buildir(), ".gens", "rules", "modules", "cache")
- target:add("cxxflags", "-fmodules-cache-path=" .. cachedir, {force = true})
- target:add("cxxflags", "-fimplicit-modules", "-fimplicit-module-maps", "-fprebuilt-module-path=" .. cachedir, {force = true})
-end
-
--- build module files
-function build_with_batchjobs(target, batchjobs, sourcebatch, opt)
-
- -- get modules flag
- local modulesflag
- local compinst = compiler.load("cxx", {target = target})
- if compinst:has_flags("-fmodules") then
- modulesflag = "-fmodules"
- elseif compinst:has_flags("-fmodules-ts") then
- modulesflag = "-fmodules-ts"
- end
- assert(modulesflag, "compiler(clang): does not support c++ module!")
-
- -- get the module cache directory, @note we must use same cache directory for each targets
- -- @see https://github.com/xmake-io/xmake/issues/2194
- --
- local cachedir = path.join(config.buildir(), ".gens", "rules", "modules", "cache")
-
- -- we need patch objectfiles to sourcebatch for linking module objects
- 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)
- table.insert(sourcebatch.objectfiles, objectfile)
- table.insert(sourcebatch.dependfiles, target:dependfile(objectfile))
- end
-
- -- load moduledeps
- local moduledeps, moduledeps_files = module_parser.load(target, sourcebatch, opt)
-
- -- compile module files to object files
- local count = 0
- local modulefiles = {}
- local sourcefiles_total = #sourcebatch.sourcefiles
- for i = 1, sourcefiles_total do
- local sourcefile = sourcebatch.sourcefiles[i]
- local moduleinfo = moduledeps_files[sourcefile] or {}
-
- -- make module file path, @note we need process submodule name, e.g. module.submodule.mpp -> module.submodule.pcm
- -- @see https://github.com/xmake-io/xmake/pull/1982
- local modulefile = path.join(cachedir, (moduleinfo.name or path.basename(sourcefile)) .. ".pcm")
- table.insert(modulefiles, modulefile)
-
- -- make build job
- moduleinfo.job = batchjobs:newjob(sourcefile, function (index, total)
-
- -- compile module files to *.pcm
- local opt2 = table.join(opt, {configs = {force = {cxxflags = {modulesflag,
- "-fimplicit-modules", "-fimplicit-module-maps", "-fprebuilt-module-path=" .. cachedir,
- "--precompile", "-x c++-module", "-fmodules-cache-path=" .. cachedir}}}})
- opt2.progress = (index * 100) / total
- opt2.objectfile = modulefiles[i]
- opt2.dependfile = target:dependfile(opt2.objectfile)
- opt2.sourcekind = assert(sourcebatch.sourcekind, "%s: sourcekind not found!", sourcefile)
- objectbuilder.build_object(target, sourcefile, opt2)
-
- -- compile *.pcm to object files
- opt2.configs = {force = {cxxflags = {modulesflag, "-fmodules-cache-path=" .. cachedir,
- "-fimplicit-modules", "-fimplicit-module-maps", "-fprebuilt-module-path=" .. cachedir}}}
- opt2.quiet = true
- opt2.objectfile = sourcebatch.objectfiles[i]
- opt2.dependfile = sourcebatch.dependfiles[i]
- objectbuilder.build_object(target, modulefiles[i], opt2)
-
- -- add module flags to other c++ files after building all modules
- count = count + 1
- if count == sourcefiles_total then
- target:add("cxxflags", modulesflag, "-fmodules-cache-path=" .. cachedir, {force = true})
- -- FIXME It is invalid for the module implementation unit
- --target:add("cxxflags", "-fimplicit-modules", "-fimplicit-module-maps", "-fprebuilt-module-path=" .. cachedir, {force = true})
- for _, modulefile in ipairs(modulefiles) do
- target:add("cxxflags", "-fmodule-file=" .. modulefile, {force = true})
- end
- end
- end)
- end
-
- -- build batchjobs
- module_parser.build_batchjobs(moduledeps, batchjobs, opt.rootjob)
-end
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 3bf8b8fcc..000000000
--- a/xmake/rules/c++/modules/build_modules/gcc.lua
+++ /dev/null
@@ -1,84 +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("private.action.build.object", {alias = "objectbuilder"})
-import("module_parser")
-
--- load parent target with modules files
-function load_parent(target, opt)
- -- get modules flag
- 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!")
-
- -- add module flags
- target:add("cxxflags", modulesflag)
-end
-
--- build module files
-function build_with_batchjobs(target, batchjobs, sourcebatch, opt)
-
- -- get modules flag
- 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!")
-
- -- we need patch objectfiles to sourcebatch for linking module objects
- 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)
- table.insert(sourcebatch.objectfiles, objectfile)
- table.insert(sourcebatch.dependfiles, target:dependfile(objectfile))
- end
-
- -- load moduledeps
- local moduledeps, moduledeps_files = module_parser.load(target, sourcebatch, opt)
-
- -- compile module files to object files
- for i = 1, #sourcebatch.sourcefiles do
- local sourcefile = sourcebatch.sourcefiles[i]
- local moduleinfo = assert(moduledeps_files[sourcefile], "moduleinfo(%s) not found!", sourcefile)
- moduleinfo.job = batchjobs:newjob(sourcefile, function (index, total)
- local opt2 = table.join(opt, {configs = {force = {cxxflags = {"-x c++"}}}})
- opt2.progress = (index * 100) / total
- opt2.objectfile = sourcebatch.objectfiles[i]
- opt2.dependfile = sourcebatch.dependfiles[i]
- opt2.sourcekind = assert(sourcebatch.sourcekind, "%s: sourcekind not found!", sourcefile)
- objectbuilder.build_object(target, sourcefile, opt2)
- end)
- end
-
- -- add module flags
- target:add("cxxflags", modulesflag)
-
- -- build batchjobs
- module_parser.build_batchjobs(moduledeps, batchjobs, opt.rootjob)
-end
-
diff --git a/xmake/rules/c++/modules/build_modules/module_parser.lua b/xmake/rules/c++/modules/build_modules/module_parser.lua
deleted file mode 100644
index 91a09aadb..000000000
--- a/xmake/rules/c++/modules/build_modules/module_parser.lua
+++ /dev/null
@@ -1,149 +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 module_parser.lua
---
-
--- imports
-import("core.project.depend")
-import("core.base.hashset")
-
--- get depend file of module source file
-function _get_dependfile_of_modulesource(target, sourcefile)
- return target:dependfile(sourcefile)
-end
-
--- get depend file of module object file, compiler will rewrite it
-function _get_dependfile_of_moduleobject(target, sourcefile)
- local objectfile = target:objectfile(sourcefile)
- return target:dependfile(objectfile)
-end
-
--- generate module deps for the given file
-function _generate_moduledeps(target, sourcefile, opt)
- local dependfile = _get_dependfile_of_modulesource(target, sourcefile)
- depend.on_changed(function ()
-
- -- trace
- vprint("generating.moduledeps %s", sourcefile)
-
- -- generating deps
- 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
- -- partition? import :xxx;
- if module_depname:startswith(":") then
- module_depname = module_name .. module_depname
- end
- module_deps = module_deps or {}
- table.insert(module_deps, module_depname)
- end
- end
-
- -- save depend data
- if module_name then
- local dependinfo = {moduleinfo = {name = module_name, deps = module_deps, file = sourcefile}}
- return dependinfo
- end
-
- end, {dependfile = dependfile, files = {sourcefile}})
-end
-
--- build batch jobs with deps
-function _build_batchjobs_with_deps(moduledeps, batchjobs, rootjob, jobrefs, moduleinfo)
- local targetjob_ref = jobrefs[moduleinfo.name]
- if targetjob_ref then
- batchjobs:add(targetjob_ref, rootjob)
- else
- local modulejob = batchjobs:add(moduleinfo.job, rootjob)
- if modulejob then
- jobrefs[moduleinfo.name] = modulejob
- for _, depname in ipairs(moduleinfo.deps) do
- local dep = moduledeps[depname]
- if dep then -- maybe nil, e.g. `import <string>;`
- _build_batchjobs_with_deps(moduledeps, batchjobs, modulejob, jobrefs, dep)
- end
- end
- end
- end
-end
-
--- generate module deps
-function generate(target, sourcebatch, opt)
- for _, sourcefile in ipairs(sourcebatch.sourcefiles) do
- _generate_moduledeps(target, sourcefile, opt)
- end
-end
-
--- load module deps
-function load(target, sourcebatch, opt)
-
- -- do generate first
- generate(target, sourcebatch, opt)
-
- -- load deps
- local moduledeps
- for _, sourcefile in ipairs(sourcebatch.sourcefiles) do
- local dependfile = _get_dependfile_of_modulesource(target, sourcefile)
- if os.isfile(dependfile) then
- local data = io.load(dependfile)
- if data then
- local moduleinfo = data.moduleinfo
- if moduleinfo then
- moduledeps = moduledeps or {}
- moduledeps[moduleinfo.name] = moduleinfo
- end
- end
- end
- end
-
- -- get moduledeps with file map
- local moduledeps_files = {}
- for _, moduleinfo in pairs(moduledeps) do
- moduledeps_files[moduleinfo.file] = moduleinfo
- end
- return moduledeps, moduledeps_files
-end
-
--- build batch jobs
-function build_batchjobs(moduledeps, batchjobs, rootjob)
- local depset = hashset.new()
- for _, moduleinfo in pairs(moduledeps) do
- assert(moduleinfo.job)
- for _, depname in ipairs(moduleinfo.deps) do
- depset:insert(depname)
- end
- end
- local moduledeps_root = {}
- for _, moduleinfo in pairs(moduledeps) do
- if not depset:has(moduleinfo.name) then
- table.insert(moduledeps_root, moduleinfo)
- end
- end
- local jobrefs = {}
- for _, moduleinfo in pairs(moduledeps_root) do
- _build_batchjobs_with_deps(moduledeps, batchjobs, rootjob, jobrefs, moduleinfo)
- 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 0a8976468..000000000
--- a/xmake/rules/c++/modules/build_modules/msvc.lua
+++ /dev/null
@@ -1,178 +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("private.action.build.object", {alias = "objectbuilder"})
-import("module_parser")
-
--- load parent target with modules files
-function load_parent(target, opt)
-
- -- get modules flag
- local modulesflag
- local compinst = compiler.load("cxx", {target = target})
- if compinst:has_flags("/experimental:module", "cxxflags") then
- modulesflag = "/experimental:module"
- end
- assert(modulesflag, "compiler(msvc): does not support c++ module!")
-
- -- add module flags
- target:add("cxxflags", modulesflag)
-
- -- get output flag
- if compinst:has_flags("/ifcOutput", "cxxflags") then
- for _, dep in ipairs(target:orderdeps()) do
- local sourcebatches = dep:sourcebatches()
- if sourcebatches and sourcebatches["c++.build.modules"] then
- local cachedir = path.join(dep:autogendir(), "rules", "modules", "cache")
- target:add("cxxflags", {"/ifcSearchDir", cachedir}, {force = true, expand = false})
- end
- end
- end
-end
-
--- build module files
-function build_with_batchjobs(target, batchjobs, sourcebatch, opt)
-
- -- get modules flag
- local modulesflag
- local compinst = compiler.load("cxx", {target = target})
- 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 cachedir
- local outputflag
- if compinst:has_flags("/ifcOutput", "cxxflags") then
- outputflag = "/ifcOutput"
- cachedir = path.join(target:autogendir(), "rules", "modules", "cache")
- if not os.isdir(cachedir) then
- os.mkdir(cachedir)
- end
- elseif compinst:has_flags("/module:output", "cxxflags") then
- outputflag = "/module:output"
- end
- assert(outputflag, "compiler(msvc): does not support c++ module!")
-
- -- get interface flag
- local interfaceflag
- if compinst:has_flags("/interface", "cxxflags") then
- interfaceflag = "/interface"
- elseif compinst:has_flags("/module:interface", "cxxflags") then
- interfaceflag = "/module: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"
- elseif compinst:has_flags("/module:interface", "cxxflags") then
- referenceflag = "/module: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"
- elseif compinst:has_flags("/module:stdIfcDir", "cxxflags") then
- stdifcdirflag = "/module:stdIfcDir"
- end
- assert(stdifcdirflag, "compiler(msvc): does not support c++ module!")
-
- -- we need patch objectfiles to sourcebatch for linking module objects
- 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
-
- -- load moduledeps
- local moduledeps, moduledeps_files = module_parser.load(target, sourcebatch, opt)
-
- -- get modulefiles
- local modulefiles = {}
- for _, sourcefile in ipairs(sourcebatch.sourcefiles) do
- local moduleinfo = moduledeps_files[sourcefile] or {}
- -- make module file path, @note we need process submodule name, e.g. module.submodule.mpp -> module.submodule.ifc
- -- @see https://github.com/xmake-io/xmake/issues/2614
- local modulefile = (cachedir and path.join(cachedir, moduleinfo.name or path.basename(sourcefile)) or objectfile) .. ".ifc"
- table.insert(modulefiles, modulefile)
- end
-
- -- compile module files to object files
- local count = 0
- local sourcefiles_total = #sourcebatch.sourcefiles
- for i = 1, sourcefiles_total do
- local sourcefile = sourcebatch.sourcefiles[i]
- local moduleinfo = assert(moduledeps_files[sourcefile], "moduleinfo(%s) not found!", sourcefile)
- moduleinfo.job = batchjobs:newjob(sourcefile, function (index, total)
- local opt2 = table.join(opt, {configs = {force = {cxxflags = {
- interfaceflag,
- {outputflag, modulefiles[i]},
- "/TP"}}}})
- opt2.progress = (index * 100) / total
- opt2.objectfile = sourcebatch.objectfiles[i]
- opt2.dependfile = sourcebatch.dependfiles[i]
- opt2.sourcekind = assert(sourcebatch.sourcekind, "%s: sourcekind not found!", sourcefile)
- objectbuilder.build_object(target, sourcefile, opt2)
-
- -- add module flags to other c++ files after building all modules
- count = count + 1
- if count == sourcefiles_total and not cachedir then
- for _, modulefile in ipairs(modulefiles) do
- target:add("cxxflags", {referenceflag, modulefile}, {force = true, expand = false})
- end
- end
- end)
- end
-
- -- add module flags
- target:add("cxxflags", modulesflag)
- if cachedir then
- target:add("cxxflags", {"/ifcSearchDir", cachedir}, {force = true, expand = false})
- end
- if stdifcdirflag then
- 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
-
- -- build batchjobs
- module_parser.build_batchjobs(moduledeps, batchjobs, opt.rootjob)
-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..49dbee0a2
--- /dev/null
+++ b/xmake/rules/c++/modules/modules_support/clang.lua
@@ -0,0 +1,384 @@
+--!A cross-platform build utility based on Lua
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-- Copyright (C) 2015-present, TBOOX Open Source Group.
+--
+-- @author ruki
+-- @file clang.lua
+--
+
+-- imports
+import("core.tool.compiler")
+import("core.project.project")
+import("core.project.depend")
+import("core.project.config")
+import("utils.progress")
+import("private.action.build.object", {alias = "objectbuilder"})
+import("common")
+
+-- load parent target with modules files
+function load_parent(target, opt)
+ local cachedir = common.get_cache_dir(target)
+
+ local prebuiltmodulepathflag = get_prebuiltmodulepathflag(target)
+
+ for _, dep in ipairs(target:orderdeps()) do
+ cachedir = common.get_cache_dir(dep)
+ 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})
+ local cachedir = common.get_cache_dir(target)
+ local stlcachedir = common.get_stlcache_dir(target)
+
+ -- 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)
+
+ -- add module flags
+ target:add("cxxflags", modulesflag)
+
+ -- 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})
+end
+
+function toolchain_include_directories(target)
+ local includedirs = _g.includedirs
+ if includedirs == nil then
+ includedirs = {}
+
+ 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 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 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 = {}
+ 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 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 = {}
+ 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
+
+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
new file mode 100644
index 000000000..e706f4e81
--- /dev/null
+++ b/xmake/rules/c++/modules/modules_support/common.lua
@@ -0,0 +1,439 @@
+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")
+
+_g.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 hashset.from(_g.stl_headers)
+end
+
+function is_stl_header(header)
+ return get_stl_headers():has(header)
+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 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 contains_modules(target)
+ 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
+ target_with_modules = true
+ break
+ end
+ end
+ return target_with_modules
+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..0c8027b6e
--- /dev/null
+++ b/xmake/rules/c++/modules/modules_support/gcc.lua
@@ -0,0 +1,314 @@
+--!A cross-platform build utility based on Lua
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-- Copyright (C) 2015-present, TBOOX Open Source Group.
+--
+-- @author ruki
+-- @file gcc.lua
+--
+
+-- imports
+import("core.tool.compiler")
+import("core.project.project")
+import("core.project.depend")
+import("core.project.config")
+import("utils.progress")
+import("private.action.build.object", {alias = "objectbuilder"})
+import("common")
+
+-- get and create the path of module mapper
+function get_module_mapper()
+ local mapper_file = path.join(config.buildir(), "mapper.txt")
+ if not os.isfile(mapper_file) then
+ io.writefile(mapper_file, "")
+ end
+
+ return mapper_file
+end
+
+-- add a module or header unit into the mapper
+function add_module_to_mapper(file, module, bmi)
+ for line in io.lines(file) do
+ if line:startswith(module .. " ") then
+ return false
+ end
+ end
+
+ local f = io.open(file, "a")
+ f:print("%s %s", module, bmi)
+ f:close()
+
+ return true
+end
+
+-- load parent target with modules files
+function load_parent(target, opt)
+end
+
+-- check C++20 module support
+function check_module_support(target)
+ local compinst = compiler.load("cxx", {target = target})
+
+ local modulesflag = get_modulesflag(target)
+ local modulemapperflag = get_modulemapperflag(target)
+
+ target:add("cxxflags", modulesflag)
+
+ if os.isfile(get_module_mapper()) then
+ os.rm(get_module_mapper())
+ end
+ target:add("cxxflags", modulemapperflag .. get_module_mapper(), {force = true, expand = false})
+end
+
+-- provide toolchain include dir for stl headerunit when p1689 is not supported
+function toolchain_include_directories(target)
+ 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 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)
+
+ 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 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
+
+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
new file mode 100644
index 000000000..1851d678c
--- /dev/null
+++ b/xmake/rules/c++/modules/modules_support/msvc.lua
@@ -0,0 +1,399 @@
+--!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"})
+import("common")
+
+-- load parent target with modules files
+function load_parent(target, opt)
+ local ifcsearchdirflag = get_ifcsearchdirflag(target)
+
+ for _, dep in ipairs(target:orderdeps()) do
+ cachedir = common.get_cache_dir(dep)
+ target:add("cxxflags", {ifcsearchdirflag, cachedir}, {force = true, expand = false})
+ end
+end
+
+-- check C++20 module support
+function check_module_support(target)
+ local compinst = target:compiler("cxx")
+ 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})
+
+ -- 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")
+ 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
+
+-- 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 scandependenciesflag = get_scandependenciesflag(target)
+
+ local cachedir = common.get_cache_dir(target)
+ local common_args = {"/TP", 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 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 = {"/TP", 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")
+
+ -- 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 = {"/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, {interfaceflag, ifcoutputflag, bmifile, provide.sourcefile})
+
+ batchcmds:add_depfiles(provide.sourcefile)
+ batchcmds:set_depmtime(os.mtime(bmifile))
+ batchcmds:set_depcache(target:dependfile(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})
+
+ 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
+
+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 12132da54..313d932f9 100644
--- a/xmake/rules/c++/modules/xmake.lua
+++ b/xmake/rules/c++/modules/xmake.lua
@@ -21,45 +21,127 @@
-- 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)
+ 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 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
- 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)
- if target:has_tool("cxx", "clang", "clangxx") then
- import("build_modules.clang").load_parent(target, opt)
- elseif target:has_tool("cxx", "gcc", "gxx") then
- import("build_modules.gcc").load_parent(target, opt)
- elseif target:has_tool("cxx", "cl") then
- import("build_modules.msvc").load_parent(target, opt)
- else
- local _, toolname = target:tool("cxx")
- raise("compiler(%s): does not support c++ module!", toolname)
+
+ -- 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)
+ end
+ end)
+
+
+rule("c++.build.modules.dependencies")
+ before_build(function(target, opt)
+ if not target:data("cxx.has_modules") then
+ return
+ end
+
+ import("modules_support.common")
+ local modules_support = common.modules_support(target)
+
+ -- build dependency data
+ local batch = target:sourcebatches()["c++.build.modules.builder"]
+ common.patch_sourcebatch(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)
+
+ target:data_set("cxx.modules", modules)
+ end)
+
+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:data("cxx.has_modules") then
+ sourcebatch.objectfiles = {}
+ return
+ end
+
+ import("modules_support.common")
+ local modules_support = common.modules_support(target)
+
+ 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 = modules_support.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)
+
+ modules_support.build_modules(target, batchcmds, objectfiles, modules, opt)
end)
- before_build_files(function (target, batchjobs, sourcebatch, opt)
- if target:has_tool("cxx", "clang", "clangxx") then
- import("build_modules.clang").build_with_batchjobs(target, batchjobs, sourcebatch, opt)
- elseif target:has_tool("cxx", "gcc", "gxx") then
- import("build_modules.gcc").build_with_batchjobs(target, batchjobs, sourcebatch, opt)
- elseif target:has_tool("cxx", "cl") then
- import("build_modules.msvc").build_with_batchjobs(target, batchjobs, sourcebatch, opt)
- else
- local _, toolname = target:tool("cxx")
- raise("compiler(%s): does not support c++ module!", toolname)
+
+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
- end, {batch = true})
+ local sourcebatch = target:sourcebatches()["c++.build.modules.install"]
+ if sourcebatch then
+ target:add("installfiles", sourcebatch.sourcefiles, {prefixdir = "include"})
+ end
+ end) \ No newline at end of file