summaryrefslogtreecommitdiff
path: root/xmake/rules/c++/modules/modules_support
diff options
context:
space:
mode:
authorArthur LAURENT <[email protected]>2024-01-23 18:24:09 +0100
committerArthur LAURENT <[email protected]>2024-01-26 18:20:58 +0100
commit82714f780177852367049edeeb047bb75ca88007 (patch)
tree62e21f89fe8e8ca1dcc28b376d0a71e180beee3d /xmake/rules/c++/modules/modules_support
parentda5f589a7ea6dfce340b92aad88db8b84aebd5bd (diff)
refactor msvc module infrastructure
Diffstat (limited to 'xmake/rules/c++/modules/modules_support')
-rw-r--r--xmake/rules/c++/modules/modules_support/msvc.lua821
-rw-r--r--xmake/rules/c++/modules/modules_support/msvc/builder.lua322
-rw-r--r--xmake/rules/c++/modules/modules_support/msvc/compiler_support.lua239
-rw-r--r--xmake/rules/c++/modules/modules_support/msvc/dependency_scanner.lua72
4 files changed, 633 insertions, 821 deletions
diff --git a/xmake/rules/c++/modules/modules_support/msvc.lua b/xmake/rules/c++/modules/modules_support/msvc.lua
deleted file mode 100644
index e90ae3df3..000000000
--- a/xmake/rules/c++/modules/modules_support/msvc.lua
+++ /dev/null
@@ -1,821 +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, Arthapz
--- @file msvc.lua
---
-
--- imports
-import("core.base.option")
-import("core.base.json")
-import("core.tool.compiler")
-import("core.project.project")
-import("core.project.depend")
-import("core.project.config")
-import("core.base.semver")
-import("utils.progress")
-import("private.action.build.object", {alias = "objectbuilder"})
-import("common")
-
--- add a module or header unit into the mapper
---
--- e.g
--- /reference Foo=build/.gens/Foo/rules/modules/cache/Foo.ifc
--- /headerUnit:angle glm/mat4x4.hpp=Users\arthu\AppData\Local\.xmake\packages\g\glm\0.9.9+8\91454f3ee0be416cb9c7452970a2300f\include\glm\mat4x4.hpp.ifc
---
-function _add_module_to_mapper(target, argument, namekey, path, objectfile, bmifile, deps)
- local modulemap = _get_modulemap_from_mapper(target)
- if modulemap[namekey] then
- return
- end
- local mapflag = {argument, path .. "=" .. bmifile}
- modulemap[namekey] = {flag = mapflag, objectfile = objectfile, deps = deps}
- common.localcache():set2(_mapper_cachekey(target), "modulemap", modulemap)
-end
-
-function _mapper_cachekey(target)
- local mode = config.mode()
- return target:name() .. "_modulemap_" .. (mode or "")
-end
-
--- flush mapper file cache
-function _flush_mapper(target)
- -- not using set2/get2 to flush only current target mapper
- common.localcache():save(_mapper_cachekey(target))
-end
-
--- get modulemap from mapper
-function _get_modulemap_from_mapper(target)
- return common.localcache():get2(_mapper_cachekey(target), "modulemap") or {}
-end
-
--- do compile
-function _compile(target, flags, sourcefile)
- local compinst = target:compiler("cxx")
- local msvc = target:toolchain("msvc")
- local compflags = compinst:compflags({sourcefile = sourcefile, target = target})
- os.vrunv(compinst:program(), winos.cmdargv(table.join(compflags or {}, flags)), {envs = msvc:runenvs()})
-end
-
--- do compile for batchcmds
--- @note we need to use batchcmds:compilev to translate paths in compflags for generator, e.g. -Ixx
-function _batchcmds_compile(batchcmds, target, flags, sourcefile)
- local compinst = target:compiler("cxx")
- local compflags = compinst:compflags({sourcefile = sourcefile, target = target})
- batchcmds:compilev(table.join(compflags or {}, flags), {compiler = compinst, sourcekind = "cxx"})
-end
-
--- add an objectfile to the linker flags
---
--- e.g
--- foo.obj
---
-function _add_objectfile_to_link_arguments(target, objectfile)
- local cachekey = target:name() .. "dependency_objectfiles"
- local cache = common.localcache():get(cachekey) or {}
- if table.contains(cache, objectfile) then
- return
- end
- table.insert(cache, objectfile)
- common.localcache():set(cachekey, cache)
- common.localcache():save(cachekey)
-end
-
--- build module file
-function _build_modulefile(target, sourcefile, opt)
- local objectfile = opt.objectfile
- local dependfile = opt.dependfile
- local compinst = compiler.load("cxx", {target = target})
- local compflags = compinst:compflags({sourcefile = sourcefile, target = target})
- local dependinfo = target:is_rebuilt() and {} or (depend.load(dependfile) or {})
-
- -- need build this object?
- local dryrun = option.get("dry-run")
- local depvalues = {compinst:program(), compflags}
- local lastmtime = os.isfile(objectfile) and os.mtime(dependfile) or 0
- if not dryrun and not depend.is_changed(dependinfo, {lastmtime = lastmtime, values = depvalues}) then
- return
- end
-
- -- init flags
- local flags = table.join("-TP", compflags, opt.flags or {})
-
- -- trace
- progress.show(opt.progress, "${color.build.object}compiling.module.$(mode) %s", opt.name)
- vprint(compinst:compcmd(sourcefile, objectfile, {compflags = flags, rawargs = true}))
-
- if not dryrun then
-
- -- do compile
- dependinfo.files = {}
- assert(compinst:compile(sourcefile, objectfile, {dependinfo = dependinfo, compflags = flags}))
-
- -- update files and values to the dependent file
- dependinfo.values = depvalues
- table.join2(dependinfo.files, sourcefile)
- depend.save(dependinfo, dependfile)
- end
-end
-
--- load module support for the current target
-function load(target)
-
- -- add modules flags if visual studio version is < 16.11 (vc 14.29)
- local modulesflag = get_modulesflag(target)
- local msvc = target:toolchain("msvc")
- local vcvars = msvc:config("vcvars")
- if vcvars.VCInstallDir and vcvars.VCToolsVersion and semver.compare(vcvars.VCToolsVersion, "14.29") < 0 then
- target:add("cxxflags", modulesflag)
- end
-
- -- enable std modules if c++23 by defaults
- if target:data("c++.msvc.enable_std_import") == nil then
- local languages = target:get("languages")
- local isatleastcpp23 = false
- for _, language in ipairs(languages) do
- if language:startswith("c++") or language:startswith("cxx") then
- isatleastcpp23 = true
- local version = tonumber(language:match("%d+"))
- if (not version or version <= 20) and not language:match("latest") then
- isatleastcpp23 = false
- break
- end
- end
- end
- local stdmodulesdir
- local msvc = target:toolchain("msvc")
- if msvc then
- local vcvars = msvc:config("vcvars")
- if vcvars.VCInstallDir and vcvars.VCToolsVersion and semver.compare(vcvars.VCToolsVersion, "14.35") > 0 then
- stdmodulesdir = path.join(vcvars.VCInstallDir, "Tools", "MSVC", vcvars.VCToolsVersion, "modules")
- end
- end
- target:data_set("c++.msvc.enable_std_import", isatleastcpp23 and os.isdir(stdmodulesdir))
- end
-end
-
--- provide toolchain include dir for stl headerunit when p1689 is not supported
-function toolchain_includedirs(target)
- for _, toolchain_inst in ipairs(target:toolchains()) do
- if toolchain_inst:name() == "msvc" then
- local vcvars = toolchain_inst:config("vcvars")
- if vcvars.VCInstallDir and vcvars.VCToolsVersion then
- return { path.join(vcvars.VCInstallDir, "Tools", "MSVC", vcvars.VCToolsVersion, "include") }
- end
- break
- end
- end
- raise("msvc toolchain includedirs not found!")
-end
-
--- generate dependency files
-function generate_dependencies(target, sourcebatch, opt)
- local msvc = target:toolchain("msvc")
- local scandependenciesflag = get_scandependenciesflag(target)
- local ifcoutputflag = get_ifcoutputflag(target)
- local common_flags = {"-TP", scandependenciesflag}
- local changed = false
- for _, sourcefile in ipairs(sourcebatch.sourcefiles) do
- local dependfile = target:dependfile(sourcefile)
- depend.on_changed(function ()
- if opt.progress then
- progress.show(opt.progress, "${color.build.object}generating.module.deps %s", sourcefile)
- end
- local outputdir = common.get_outputdir(target, sourcefile)
-
- local jsonfile = path.join(outputdir, path.filename(sourcefile) .. ".module.json")
- if scandependenciesflag and not target:policy("build.c++.msvc.fallbackscanner") then
- local flags = {jsonfile, sourcefile, ifcoutputflag, outputdir, "-Fo" .. target:objectfile(sourcefile)}
- _compile(target, table.join(common_flags, flags), sourcefile)
- else
- common.fallback_generate_dependencies(target, jsonfile, sourcefile, function(file)
- local compinst = target:compiler("cxx")
- local compflags = compinst:compflags({sourcefile = file, target = target})
- local ifile = path.translate(path.join(outputdir, path.filename(file) .. ".i"))
- os.vrunv(compinst:program(), table.join(compflags,
- {"/P", "-TP", file, "/Fi" .. ifile}), {envs = msvc:runenvs()})
- local content = io.readfile(ifile)
- os.rm(ifile)
- return content
- end)
- end
- changed = true
-
- local dependinfo = io.readfile(jsonfile)
- return { moduleinfo = dependinfo }
- end, {dependfile = dependfile, files = {sourcefile}, changed = target:is_rebuilt()})
- end
- return changed
-end
-
--- generate header unit module bmi for batchjobs
-function generate_headerunit_for_batchjob(target, name, flags, objectfile, index, total)
- -- don't generate same header unit bmi at the same time across targets
- if not common.memcache():get2(name, "generating") then
- local common_flags = {"-TP", "-c"}
- common.memcache():set2(name, "generating", true)
- progress.show((index * 100) / total, "${color.build.object}compiling.headerunit.$(mode) %s", name)
- _compile(target, table.join(common_flags, flags))
- _add_objectfile_to_link_arguments(target, objectfile)
- common.memcache():set2(name, "generating", false)
- end
-end
-
--- generate header unit module bmi for batchcmds
-function generate_headerunit_for_batchcmds(target, name, flags, objectfile, batchcmds, opt)
- local common_flags = {"-TP", "-c"}
- batchcmds:show_progress(opt.progress, "${color.build.object}compiling.headerunit.$(mode) %s", name)
- _batchcmds_compile(batchcmds, target, table.join(common_flags, flags))
- _add_objectfile_to_link_arguments(target, objectfile)
-end
-
--- generate target stl header unit modules for batchjobs
-function generate_stl_headerunits_for_batchjobs(target, batchjobs, headerunits, opt)
- local stlcachedir = common.stlmodules_cachedir(target, {mkdir = true})
-
- -- get flags
- local exportheaderflag = get_exportheaderflag(target)
- local headerunitflag = get_headerunitflag(target)
- local headernameflag = get_headernameflag(target)
- local ifcoutputflag = get_ifcoutputflag(target)
- assert(headerunitflag and headernameflag and exportheaderflag, "compiler(msvc): does not support c++ header units!")
-
- -- flush job
- local flushjob = batchjobs:addjob(target:name() .. "_stl_headerunits_flush_mapper", function(index, total)
- _flush_mapper(target)
- end, {rootjob = opt.rootjob})
-
- -- build headerunits
- for _, headerunit in ipairs(headerunits) do
- local bmifile = path.join(stlcachedir, headerunit.name .. get_bmi_extension())
- local objectfile = bmifile .. ".obj"
- batchjobs:addjob(headerunit.name, function(index, total)
- depend.on_changed(function()
- local flags = {
- exportheaderflag,
- headernameflag .. ":angle",
- headerunit.name,
- ifcoutputflag,
- headerunit.name:startswith("experimental/") and path.join(stlcachedir, "experimental") or stlcachedir,
- "-Fo" .. objectfile
- }
- generate_headerunit_for_batchjob(target, headerunit.name, flags, objectfile, index, total)
-
- end, {dependfile = target:dependfile(bmifile), files = {headerunit.path}, changed = target:is_rebuilt()})
- _add_module_to_mapper(target, headerunitflag .. ":angle", headerunit.name, headerunit.name, objectfile, bmifile)
- end, {rootjob = flushjob})
- end
-end
-
--- generate target stl header units for batchcmds
-function generate_stl_headerunits_for_batchcmds(target, batchcmds, headerunits, opt)
- local stlcachedir = common.stlmodules_cachedir(target, {mkdir = true})
- local exportheaderflag = get_exportheaderflag(target)
- local headerunitflag = get_headerunitflag(target)
- local headernameflag = get_headernameflag(target)
- local ifcoutputflag = get_ifcoutputflag(target)
- assert(headerunitflag and headernameflag and exportheaderflag, "compiler(msvc): does not support c++ header units!")
-
- -- build headerunits
- local depmtime = 0
- for _, headerunit in ipairs(headerunits) do
- local bmifile = path.join(stlcachedir, headerunit.name .. get_bmi_extension())
- local objectfile = bmifile .. ".obj"
- local flags = {
- exportheaderflag,
- headernameflag .. ":angle",
- headerunit.name,
- ifcoutputflag,
- path(headerunit.name:startswith("experimental/") and path.join(stlcachedir, "experimental") or stlcachedir),
- path(objectfile, function (p) return "-Fo" .. p end)}
- generate_headerunit_for_batchcmds(target, headerunit.name, flags, objectfile, batchcmds, opt)
- batchcmds:add_depfiles(headerunit.path)
- _add_module_to_mapper(target, headerunitflag .. ":angle", headerunit.name, headerunit.name, objectfile, bmifile)
- depmtime = math.max(depmtime, os.mtime(bmifile))
- end
- batchcmds:set_depmtime(depmtime)
- _flush_mapper(target)
-end
-
--- generate target user header units for batchcmds
-function generate_user_headerunits_for_batchjobs(target, batchjobs, headerunits, opt)
- -- get flags
- local exportheaderflag = get_exportheaderflag(target)
- local headerunitflag = get_headerunitflag(target)
- local headernameflag = get_headernameflag(target)
- local ifcoutputflag = get_ifcoutputflag(target)
- assert(headerunitflag and headernameflag and exportheaderflag, "compiler(msvc): does not support c++ header units!")
-
- -- flush job
- local flushjob = batchjobs:addjob(target:name() .. "_user_headerunits_flush_mapper", function(index, total)
- _flush_mapper(target)
- end, {rootjob = opt.rootjob})
-
- -- build headerunits
- for _, headerunit in ipairs(headerunits) do
- local file = path.relative(headerunit.path, target:scriptdir())
- local objectfile = target:objectfile(file)
- local outputdir = common.get_outputdir(target, headerunit)
- local bmifilename = path.basename(objectfile) .. get_bmi_extension()
- local bmifile = path.join(outputdir, bmifilename)
- batchjobs:addjob(headerunit.name, function (index, total)
- depend.on_changed(function()
- local objectdir = path.directory(objectfile)
- if not os.isdir(objectdir) then
- os.mkdir(objectdir)
- end
- if not os.isdir(outputdir) then
- os.mkdir(outputdir)
- end
-
- -- generate headerunit
- local flags = {
- exportheaderflag,
- headernameflag .. headerunit.type,
- headerunit.path,
- ifcoutputflag,
- outputdir,
- "/Fo" .. objectfile
- }
- generate_headerunit_for_batchjob(target, headerunit.name, flags, objectfile, index, total)
- end, {dependfile = target:dependfile(bmifile), files = {headerunit.path}, changed = target:is_rebuilt()})
- _add_module_to_mapper(target, headerunitflag, headerunit.name, headerunit.path, objectfile, bmifile)
- end, {rootjob = flushjob})
- end
-end
-
--- generate target user header units for batchcmds
-function generate_user_headerunits_for_batchcmds(target, batchcmds, headerunits, opt)
- local exportheaderflag = get_exportheaderflag(target)
- local headerunitflag = get_headerunitflag(target)
- local headernameflag = get_headernameflag(target)
- local ifcoutputflag = get_ifcoutputflag(target)
- assert(headerunitflag and headernameflag and exportheaderflag, "compiler(msvc): does not support c++ header units!")
-
- -- build headerunits
- local depmtime = 0
- for _, headerunit in ipairs(headerunits) do
- local file = path.relative(headerunit.path, target:scriptdir())
- local objectfile = target:objectfile(file)
- local outputdir = common.get_outputdir(target, headerunit)
- batchcmds:mkdir(outputdir)
-
- local bmifilename = path.basename(objectfile) .. get_bmi_extension()
- local bmifile = path.join(outputdir, bmifilename)
- batchcmds:mkdir(path.directory(objectfile))
-
- local flags = {
- exportheaderflag,
- headernameflag .. headerunit.type,
- headerunit.path,
- ifcoutputflag,
- outputdir,
- "/Fo" .. objectfile
- }
- generate_headerunit_for_batchcmds(target, headerunit.name, flags, objectfile, batchcmds, opt)
- batchcmds:add_depfiles(headerunit.path)
-
- _add_module_to_mapper(target, headerunitflag, headerunit.name, headerunit.path, objectfile, bmifile)
-
- depmtime = math.max(depmtime, os.mtime(bmifile))
- end
- batchcmds:set_depmtime(depmtime)
- _flush_mapper(target)
-end
-
--- build module files for batchjobs
-function build_modules_for_batchjobs(target, batchjobs, objectfiles, modules, opt)
-
- -- get flags
- local ifcoutputflag = get_ifcoutputflag(target)
- local interfaceflag = get_interfaceflag(target)
- local referenceflag = get_referenceflag(target)
- local internalpartitionflag = get_internalpartitionflag(target)
-
- -- flush job
- local flushjob = batchjobs:addjob(target:name() .. "_modules", function(index, total)
- _flush_mapper(target)
- end, {rootjob = opt.rootjob})
-
- if target:data("c++.msvc.enable_std_import") then
- for objectfile, module in pairs(get_stdmodules(target)) do
- table.insert(objectfiles, objectfile)
- modules[objectfile] = module
- modules[objectfile].external = true
- end
- end
-
- local modulesjobs = {}
- for _, objectfile in ipairs(objectfiles) do
- local module = modules[objectfile]
- if module then
- local cppfile = module.cppfile
- local name, provide
- if module.provides then
- -- assume there that provides is only one, until we encounter the case
- local length = 0
- for k, v in pairs(module.provides) do
- length = length + 1
- name = k
- provide = v
- cppfile = provide.sourcefile
- if length > 1 then
- raise("multiple provides are not supported now!")
- end
- break
- end
- end
- local moduleinfo = table.copy(provide) or {}
- local flags = {"-TP"}
- local dependfile = target:dependfile(objectfile)
-
- if provide then
- table.join2(flags, {ifcoutputflag, path(provide.bmi), provide.interface and interfaceflag or internalpartitionflag})
- dependfile = target:dependfile(provide.bmi)
-
- local fileconfig = target:fileconfig(cppfile)
- if fileconfig and fileconfig.install then
- batchjobs:addjob(name .. "_metafile", function(index, total)
- local metafilepath = common.get_metafile(target, cppfile)
- depend.on_changed(function()
- progress.show((index * 100) / total, "${color.build.object}generating.module.metadata %s", name)
- local metadata = common.generate_meta_module_info(target, name, cppfile, module.requires)
- json.savefile(metafilepath, metadata)
- end, {dependfile = target:dependfile(metafilepath), files = {cppfile}, changed = target:is_rebuilt()})
- end, {rootjob = flushjob})
- end
- end
-
- table.join2(moduleinfo, {
- name = name or cppfile,
- deps = table.keys(module.requires or {}),
- sourcefile = cppfile,
- job = batchjobs:newjob(name or cppfile, function(index, total)
- -- append module mapper flags first
- -- @note we add it at the end to ensure that the full modulemap are already stored in the mapper
- local requiresflags
- if module.requires then
- requiresflags = get_requiresflags(target, module.requires, {expand = true})
- end
- local _flags = table.join(flags, requiresflags or {})
-
- if provide or common.has_module_extension(cppfile) then
- if not common.memcache():get2(name or cppfile, "compiling") then
- if name and module.external then
- common.memcache():set2(name or cppfile, "compiling", true)
- end
- _build_modulefile(target, cppfile, {
- objectfile = objectfile,
- dependfile = dependfile,
- name = name or module.cppfile,
- flags = _flags,
- progress = (index * 100) / total})
- end
- _add_objectfile_to_link_arguments(target, objectfile)
- elseif requiresflags then
- requiresflags = get_requiresflags(target, module.requires)
- target:fileconfig_add(cppfile, {force = {cxxflags = table.join(flags, requiresflags)}})
- end
-
- if provide then
- _add_module_to_mapper(target, referenceflag, name, name, objectfile, provide.bmi, requiresflags)
- end
- end)})
- modulesjobs[name or cppfile] = moduleinfo
- end
- end
-
- -- build batchjobs for modules
- common.build_batchjobs_for_modules(modulesjobs, batchjobs, flushjob)
-end
-
--- build module files for batchcmds
-function build_modules_for_batchcmds(target, batchcmds, objectfiles, modules, opt)
-
- -- get flags
- local ifcoutputflag = get_ifcoutputflag(target)
- local interfaceflag = get_interfaceflag(target)
- local referenceflag = get_referenceflag(target)
- local internalpartitionflag = get_internalpartitionflag(target)
-
- if target:data("c++.msvc.enable_std_import") then
- for objectfile, module in pairs(get_stdmodules(target)) do
- table.insert(objectfiles, objectfile)
- modules[objectfile] = module
- end
- end
-
- -- build modules
- local depmtime = 0
- for _, objectfile in ipairs(objectfiles) do
- local module = modules[objectfile]
- if module then
- local cppfile = module.cppfile
- local name, provide
- if module.provides then
- local length = 0
- for k, v in pairs(module.provides) do
- length = length + 1
- name = k
- provide = v
- cppfile = provide.sourcefile
- if length > 1 then
- raise("multiple provides are not supported now!")
- end
- break
- end
- end
- -- append required modulemap flags to module
- local requiresflags
- if module.requires then
- requiresflags = get_requiresflags(target, module.requires, {expand = true})
- end
-
- local flags = table.join({"-TP", "-c", path(cppfile), path(objectfile, function (p) return "-Fo" .. p end)})
- if provide or common.has_module_extension(cppfile) then
- if provide then
- table.join2(flags, {ifcoutputflag, path(provide.bmi), provide.interface and interfaceflag or internalpartitionflag})
- end
-
- batchcmds:show_progress(opt.progress, "${color.build.object}compiling.module.$(mode) %s", name or cppfile)
- batchcmds:mkdir(path.directory(objectfile))
- _batchcmds_compile(batchcmds, target, table.join(flags, requiresflags or {}), cppfile)
- batchcmds:add_depfiles(cppfile)
- _add_objectfile_to_link_arguments(target, path.translate(objectfile))
- if provide then
- _add_module_to_mapper(target, referenceflag, name, name, objectfile, provide.bmi, requiresflags)
- -- add requiresflags to module. it will be used for project generation
- target:fileconfig_add(cppfile, {force = {cxxflags = requiresflags}})
- end
- depmtime = math.max(depmtime, os.mtime(provide and provide.bmi or objectfile))
- elseif requiresflags then
- requiresflags = get_requiresflags(target, module.requires)
- target:fileconfig_add(cppfile, {force = {cxxflags = table.join(flags, requiresflags)}})
- end
- end
- end
-
- batchcmds:set_depmtime(depmtime)
- _flush_mapper(target)
-end
-
-function get_stdmodules(target)
- local modules = {}
-
- -- build c++23 standard modules if needed
- if target:data("c++.msvc.enable_std_import") then
- local msvc = target:toolchain("msvc")
- if msvc then
- local vcvars = msvc:config("vcvars")
- if vcvars.VCInstallDir and vcvars.VCToolsVersion then
- local stdmodulesdir = path.join(vcvars.VCInstallDir, "Tools", "MSVC", vcvars.VCToolsVersion, "modules")
- assert(stdmodulesdir, "Can't enable C++23 std modules, directory missing !")
-
- local stlcachedir = common.stlmodules_cachedir(target)
- local modulefile = path.join(stdmodulesdir, "std.ixx")
- local bmifile = path.join(stlcachedir, "std.ixx" .. get_bmi_extension())
- local objfile = bmifile .. ".obj"
- modules[objfile] = {provides = {std = {interface = true, sourcefile = modulefile, bmi = bmifile}}}
- stlcachedir = common.stlmodules_cachedir(target)
- modulefile = path.join(stdmodulesdir, "std.compat.ixx")
- bmifile = path.join(stlcachedir, "std.compat.ixx" .. get_bmi_extension())
- objfile = bmifile .. ".obj"
- modules[objfile] = {provides = {["std.compat"] = {interface = true, sourcefile = modulefile, bmi = bmifile}}, requires = {std = {unique = false, method = "by-name"}}}
- end
- end
- end
- return modules
-end
-
-function get_bmi_extension()
- return ".ifc"
-end
-
-function get_modulesflag(target)
- local modulesflag = _g.modulesflag
- if modulesflag == nil then
- local compinst = target:compiler("cxx")
- if compinst:has_flags("-experimental:module", "cxxflags", {flagskey = "cl_experimental_module"}) then
- modulesflag = "-experimental:module"
- end
- local msvc = target:toolchain("msvc")
- local vcvars = msvc:config("vcvars")
- if vcvars.VCInstallDir and vcvars.VCToolsVersion and semver.compare(vcvars.VCToolsVersion, "14.29") < 0 then
- assert(modulesflag, "compiler(msvc): does not support c++ module!")
- end
- _g.modulesflag = modulesflag or false
- end
- return modulesflag or nil
-end
-
-function get_ifcoutputflag(target)
- local ifcoutputflag = _g.ifcoutputflag
- if ifcoutputflag == nil then
- local compinst = target:compiler("cxx")
- if compinst:has_flags({"-ifcOutput", os.tmpfile()}, "cxxflags", {flagskey = "cl_ifc_output"}) then
- ifcoutputflag = "-ifcOutput"
- end
- assert(ifcoutputflag, "compiler(msvc): does not support c++ module flag(/ifcOutput)!")
- _g.ifcoutputflag = ifcoutputflag or false
- end
- return ifcoutputflag or nil
-end
-
-function get_ifcsearchdirflag(target)
- local ifcsearchdirflag = _g.ifcsearchdirflag
- if ifcsearchdirflag == nil then
- local compinst = target:compiler("cxx")
- if compinst:has_flags({"-ifcSearchDir", os.tmpdir()}, "cxxflags", {flagskey = "cl_ifc_search_dir"}) then
- ifcsearchdirflag = "-ifcSearchDir"
- end
- assert(ifcsearchdirflag, "compiler(msvc): does not support c++ module flag(/ifcSearchDir)!")
- _g.ifcsearchdirflag = ifcsearchdirflag or false
- end
- return ifcsearchdirflag or nil
-end
-
-function get_interfaceflag(target)
- local interfaceflag = _g.interfaceflag
- if interfaceflag == nil then
- local compinst = target:compiler("cxx")
- if compinst:has_flags("-interface", "cxxflags", {flagskey = "cl_interface"}) then
- interfaceflag = "-interface"
- end
- assert(interfaceflag, "compiler(msvc): does not support c++ module flag(/interface)!")
- _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", "Foo=" .. os.tmpfile()}, "cxxflags", {flagskey = "cl_reference"}) then
- referenceflag = "-reference"
- end
- assert(referenceflag, "compiler(msvc): does not support c++ module flag(/reference)!")
- _g.referenceflag = referenceflag or false
- end
- return referenceflag or nil
-end
-
-function get_headernameflag(target)
- local headernameflag = _g.headernameflag
- if headernameflag == nil then
- local compinst = target:compiler("cxx")
- if compinst:has_flags({"-std:c++latest", "-exportHeader", "-headerName:quote"}, "cxxflags", {flagskey = "cl_header_name_quote"}) and
- compinst:has_flags({"-std:c++latest", "-exportHeader", "-headerName:angle"}, "cxxflags", {flagskey = "cl_header_name_angle"}) then
- headernameflag = "-headerName"
- end
- _g.headernameflag = headernameflag or false
- end
- return headernameflag or nil
-end
-
-function get_headerunitflag(target)
- local headerunitflag = _g.headerunitflag
- if headerunitflag == nil then
- local compinst = target:compiler("cxx")
- local ifcfile = os.tmpfile()
- if compinst:has_flags({"-std:c++latest", "-headerUnit:quote", "foo.h=" .. ifcfile}, "cxxflags", {flagskey = "cl_header_unit_quote"}) and
- compinst:has_flags({"-std:c++latest", "-headerUnit:angle", "foo.h=" .. ifcfile}, "cxxflags", {flagskey = "cl_header_unit_angle"}) then
- headerunitflag = "-headerUnit"
- end
- _g.headerunitflag = headerunitflag or false
- end
- return headerunitflag or nil
-end
-
-function get_exportheaderflag(target)
- local exportheaderflag = _g.exportheaderflag
- if exportheaderflag == nil then
- if get_headernameflag(target) then
- exportheaderflag = "-exportHeader"
- end
- _g.exportheaderflag = exportheaderflag or false
- end
- return exportheaderflag or nil
-end
-
-function get_scandependenciesflag(target)
- local scandependenciesflag = _g.scandependenciesflag
- if scandependenciesflag == nil then
- local compinst = target:compiler("cxx")
- local scan_dependencies_jsonfile = os.tmpfile() .. ".json"
- if compinst:has_flags("-scanDependencies " .. scan_dependencies_jsonfile, "cxflags", {flagskey = "cl_scan_dependencies",
- on_check = function (ok, errors)
- if os.isfile(scan_dependencies_jsonfile) then
- ok = true
- end
- if ok and not os.isfile(scan_dependencies_jsonfile) then
- ok = false
- end
- return ok, errors
- end}) then
- scandependenciesflag = "-scanDependencies"
- end
- _g.scandependenciesflag = scandependenciesflag or false
- end
- return scandependenciesflag or nil
-end
-
--- get requireflags from module mapper
-function get_requiresflags(target, requires, opt)
- opt = opt or {}
- local flags = {}
- local modulemap = _get_modulemap_from_mapper(target)
- -- add deps required module flags
- local already_mapped_modules = {}
- for name, _ in table.orderpairs(requires) do
- -- if already in flags, continue
- if already_mapped_modules[name] then
- goto continue
- end
-
- for _, dep in ipairs(target:orderdeps()) do
- local modulemap_ = _get_modulemap_from_mapper(dep)
- if modulemap_[name] then
- table.join2(flags, modulemap_[name].flag)
- -- we need to ignore headerunits from deps
- -- @see https://github.com/xmake-io/xmake/issues/3925
- local skip = 0
- for _, flag in ipairs(modulemap_[name].deps) do
- if flag:find("headerUnit:quote", 1, true) then
- skip = 2
- end
- if skip == 0 then
- table.insert(flags, flag)
- end
- if skip > 0 then
- skip = skip - 1
- end
- end
- already_mapped_modules[name] = true
- goto continue
- end
- end
-
- -- append target required module mapper flags
- if modulemap[name] then
- table.join2(flags, modulemap[name].flag)
- table.join2(flags, modulemap[name].deps or {})
- goto continue
- end
-
- ::continue::
- end
- local requireflags = {}
- local contains = {}
- for i = 1, #flags, 2 do
- local value = flags[i + 1]
- if not contains[value] then
- local key = flags[i]
- if opt.expand then
- table.insert(requireflags, key)
- table.insert(requireflags, value)
- else
- table.insert(requireflags, {key, value})
- end
- contains[value] = true
- end
- end
- if #requireflags > 0 then
- return requireflags
- end
-end
-
-function get_cppversionflag(target)
- local cppversionflag = _g.cppversionflag
- if cppversionflag == nil then
- local compinst = target:compiler("cxx")
- local flags = compinst:compflags({target = target})
- cppversionflag = table.find_if(flags, function(v) string.startswith(v, "/std:c++") end) or "/std:c++latest"
- end
- return cppversionflag or nil
-end
-
-function get_internalpartitionflag(target)
- local internalpartitionflag = _g.internalpartitionflag
- if internalpartitionflag == nil then
- local compinst = target:compiler("cxx")
- if compinst:has_flags("-internalPartition", "cxxflags", {flagskey = "cl_internal_partition"}) then
- internalpartitionflag = "-internalPartition"
- end
- _g.internalpartitionflag = internalpartitionflag or false
- end
- return internalpartitionflag or nil
-end
diff --git a/xmake/rules/c++/modules/modules_support/msvc/builder.lua b/xmake/rules/c++/modules/modules_support/msvc/builder.lua
new file mode 100644
index 000000000..0f4f9b735
--- /dev/null
+++ b/xmake/rules/c++/modules/modules_support/msvc/builder.lua
@@ -0,0 +1,322 @@
+--!A cross-platform build utility based on Lua
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-- Copyright (C) 2015-present, TBOOX Open Source Group.
+--
+-- @author ruki, Arthapz
+-- @file msvc/builder.lua
+--
+
+-- imports
+import("core.base.json")
+import("core.base.option")
+import("core.base.semver")
+import("utils.progress")
+import("private.action.build.object", {alias = "objectbuilder"})
+import("core.tool.compiler")
+import("core.project.config")
+import("core.project.depend")
+import("private.tools.vstool")
+import("compiler_support")
+import(".builder", {inherit = true})
+
+-- get flags for building a module
+function _make_modulebuildflags(target, provide, bmifile, sourcefile, objectfile, opt)
+
+ -- get flags
+ local ifcoutputflag = compiler_support.get_ifcoutputflag(target)
+ local ifconlyflag = compiler_support.get_ifconlyflag(target)
+ local interfaceflag = compiler_support.get_interfaceflag(target)
+ local internalpartitionflag = compiler_support.get_internalpartitionflag(target)
+
+ local objectfile_or_ifconlyflag = (opt.external and ifconlyflag) and ifconlyflag or "-Fo" .. objectfile
+
+ local flags
+ if provide then -- named module
+ flags = {"-TP", ifcoutputflag, bmifile, provide.interface and interfaceflag or internalpartitionflag, objectfile_or_ifconlyflag, "-c", sourcefile}
+ else
+ flags = {"-TP", objectfile_or_ifconlyflag, "-c", sourcefile}
+ end
+
+ return flags
+end
+
+-- get flags for building a headerunit
+function _make_headerunitflags(target, headerunit, bmifile, opt)
+
+ -- get flags
+ local exportheaderflag = compiler_support.get_exportheaderflag(target)
+ local headernameflag = compiler_support.get_headernameflag(target)
+ local ifcoutputflag = compiler_support.get_ifcoutputflag(target)
+ assert(headernameflag and exportheaderflag, "compiler(msvc): does not support c++ header units!")
+
+ local local_directory = (headerunit.type == ":quote") and {"-I" .. path.directory(headerunit.path)} or {}
+
+ local flags = table.join(local_directory, {"-TP",
+ exportheaderflag,
+ headernameflag .. headerunit.type,
+ ifcoutputflag,
+ bmifile,
+ headerunit.type == ":angle" and headerunit.name or headerunit.path})
+ return flags
+end
+
+-- do compile
+function _compile(target, flags, sourcefile)
+
+ local dryrun = option.get("dry-run")
+ local compinst = target:compiler("cxx")
+ local compflags = compinst:compflags({sourcefile = sourcefile, target = target})
+ local msvc = target:toolchain("msvc")
+ local flags = table.join(compflags or {}, flags)
+
+ -- trace
+ vprint(compinst:program(), table.unpack(flags))
+
+ if not dryrun then
+ -- do compile
+ assert(os.iorunv(compinst:program(), flags, {envs = msvc:runenvs()}))
+ end
+end
+
+-- do compile for batchcmds
+-- @note we need to use batchcmds:compilev to translate paths in compflags for generator, e.g. -Ixx
+function _batchcmds_compile(batchcmds, target, flags, sourcefile)
+
+ local compinst = target:compiler("cxx")
+ local compflags = compinst:compflags({sourcefile = sourcefile, target = target})
+ batchcmds:compilev(table.join(compflags or {}, flags), {compiler = compinst, sourcekind = "cxx"})
+end
+
+-- get module requires flags
+-- e.g
+-- /reference Foo=build/.gens/Foo/rules/modules/cache/Foo.ifc
+-- /headerUnit:angle glm/mat4x4.hpp=Users\arthu\AppData\Local\.xmake\packages\g\glm\0.9.9+8\91454f3ee0be416cb9c7452970a2300f\include\glm\mat4x4.hpp.ifc
+--
+function _get_requiresflags(target, module, opt)
+
+ local referenceflag = compiler_support.get_referenceflag(target)
+ local headerunitflag = compiler_support.get_headerunitflag(target)
+
+ local name = module.name
+ local cachekey = target:name() .. name
+
+ local requiresflags = compiler_support.memcache():get2(cachekey, "requiresflags")
+ or compiler_support.localcache():get2(cachekey, "requiresflags")
+ if not requiresflags or (opt and opt.regenerate) then
+ local deps_flags = {}
+ for required, _ in pairs(module.requires) do
+ local dep_module = get_from_target_mapper(target, required)
+
+ assert(dep_module, "module dependency %s required for %s not found <%s>", required, name, target:name())
+
+ local bmifile = dep_module.bmi
+ local mapflag = {dep_module.headerunit and headerunitflag .. dep_module.headerunit.type or referenceflag, required .. "=" .. bmifile}
+ table.insert(deps_flags, mapflag)
+
+ -- append deps
+ if dep_module.opt and dep_module.opt.deps then
+ local deps = _get_requiresflags(target, { name = dep_module.name or dep_module.sourcefile, bmi = bmifile, requires = dep_module.opt.deps })
+ table.join2(deps_flags, deps)
+ end
+ end
+
+ requiresflags = {}
+ -- remove duplicates
+ local contains = {}
+ for _, map in ipairs(deps_flags) do
+ local name, _ = map[2]:split("=")[1], map[2]:split("=")[2]
+ if not contains[name] then
+ table.insert(requiresflags, map)
+ contains[name] = true
+ end
+ end
+ compiler_support.memcache():set2(cachekey, "requiresflags", requiresflags)
+ compiler_support.localcache():set2(cachekey, "requiresflags", requiresflags)
+ end
+
+ return requiresflags
+end
+
+function _append_requires_flags(target, module, name, cppfile, bmifile, opt)
+ local cxxflags = {}
+ local requiresflags = _get_requiresflags(target, {name = (name or cppfile), bmi = bmifile, requires = module.requires}, {regenerate = opt.build})
+
+ for _, flag in ipairs(requiresflags) do
+ -- we need to wrap flag to support flag with space
+ if type(flag) == "string" and flag:find(" ", 1, true) then
+ table.insert(cxxflags, {flag})
+ else
+ table.insert(cxxflags, flag)
+ end
+ end
+ target:fileconfig_add(cppfile, {force = {cxxflags = cxxflags}})
+end
+
+-- populate module map
+function populate_module_map(target, modules)
+ for _, module in pairs(modules) do
+ local name, provide, cppfile = compiler_support.get_provided_module(module)
+ if provide then
+ local bmifile = compiler_support.get_bmi_path(provide.bmi)
+ add_module_to_target_mapper(target, name, cppfile, bmifile, {deps = module.requires})
+ end
+ end
+end
+
+-- get defines for a module
+function get_module_required_defines(target, sourcefile)
+ local compinst = compiler.load("cxx", {target = target})
+ local compflags = compinst:compflags({sourcefile = sourcefile, target = target})
+ local defines
+
+ for _, flag in ipairs(compflags) do
+ if flag:startswith("-D") or flag:startswith("/D") then
+ defines = defines or {}
+ table.insert(defines, flag:sub(3))
+ end
+ end
+
+ return defines
+end
+
+-- build module file for batchjobs
+function make_module_build_job(target, batchjobs, job_name, deps, opt)
+
+ local name, provide, _ = compiler_support.get_provided_module(opt.module)
+ local bmifile = provide and compiler_support.get_bmi_path(provide.bmi)
+ local dryrun = option.get("dry-run")
+ local populate_job_name = get_modulemap_populate_jobname(target)
+
+ return {
+ name = job_name,
+ deps = table.join({populate_job_name}, deps),
+ sourcefile = opt.cppfile,
+ job = batchjobs:newjob(name or opt.cppfile, function(index, total)
+
+ local compinst = compiler.load("cxx", {target = target})
+ local compflags = compinst:compflags({sourcefile = opt.cppfile, target = target})
+
+ -- append requires flags
+ if opt.module.requires then
+ _append_requires_flags(target, opt.module, name, opt.cppfile, bmifile, opt)
+ end
+
+ local dependfile = target:dependfile(bmifile or opt.objectfile)
+ local dependinfo = depend.load(dependfile) or {}
+ dependinfo.files = {}
+ local depvalues = {compinst:program(), compflags}
+
+ -- compile if it's a named module
+ if opt.build and (provide or compiler_support.has_module_extension(opt.cppfile)) then
+ progress.show((index * 100) / total, "${color.build.target}<%s> ${clear}${color.build.object}compiling.module.$(mode) %s", target:name(), name or opt.cppfile)
+
+ if not dryrun then
+ local objectdir = path.directory(opt.objectfile)
+ if not os.isdir(objectdir) then
+ os.mkdir(objectdir)
+ end
+ end
+
+ local fileconfig = target:fileconfig(opt.cppfile)
+ local external = fileconfig and fileconfig.external
+ local flags = _make_modulebuildflags(target, provide, bmifile, opt.cppfile, opt.objectfile, {external = external})
+
+ _compile(target, flags, opt.cppfile)
+ end
+
+ table.insert(dependinfo.files, opt.cppfile)
+ dependinfo.values = depvalues
+ depend.save(dependinfo, dependfile)
+ end)}
+end
+
+-- build module file for batchcmds
+function make_module_build_cmds(target, batchcmds, opt)
+
+ local name, provide, _ = compiler_support.get_provided_module(opt.module)
+ local bmifile = provide and compiler_support.get_bmi_path(provide.bmi)
+
+ -- append requires flags
+ if opt.module.requires then
+ _append_requires_flags(target, opt.module, name, opt.cppfile, bmifile, opt)
+ end
+
+ if opt.build then
+ if provide or compiler_support.has_module_extension(opt.cppfile) then
+ batchcmds:show_progress(opt.progress, "${color.build.target}<%s> ${clear}${color.build.object}compiling.module.$(mode) %s", target:name(), name or opt.cppfile)
+ batchcmds:mkdir(path.directory(opt.objectfile))
+
+ local fileconfig = target:fileconfig(opt.cppfile)
+ local external = fileconfig and fileconfig.external
+ local flags = _make_modulebuildflags(target, provide, bmifile, opt.cppfile, opt.objectfile, {batchcmds = true, external = external})
+ _batchcmds_compile(batchcmds, target, flags, opt.cppfile)
+ end
+ end
+ batchcmds:add_depfiles(opt.cppfile)
+
+ return os.mtime(opt.objectfile)
+end
+
+-- build headerunit file for batchjobs
+function make_headerunit_build_job(target, job_name, batchjobs, headerunit, bmifile, outputdir, opt)
+
+ local already_exists = add_headerunit_to_target_mapper(target, headerunit, bmifile)
+ if not already_exists then
+ return {
+ name = job_name,
+ sourcefile = headerunit.path,
+ job = batchjobs:newjob(job_name, function(index, total)
+ if not os.isdir(outputdir) then
+ os.mkdir(outputdir)
+ end
+
+ local compinst = compiler.load("cxx", {target = target})
+ local compflags = compinst:compflags({sourcefile = headerunit.path, target = target})
+
+ local dependfile = target:dependfile(bmifile)
+ local dependinfo = depend.load(dependfile) or {}
+ dependinfo.files = {}
+ local depvalues = {compinst:program(), compflags}
+
+ local name = headerunit.unique and headerunit.name or headerunit.path
+
+ if opt.build then
+ progress.show((index * 100) / total, "${color.build.target}<%s> ${clear}${color.build.object}compiling.headerunit.$(mode) %s", target:name(), headerunit.name)
+ _compile(target, _make_headerunitflags(target, headerunit, bmifile, {}), name, target:objectfile(headerunit.path), true)
+ end
+
+ table.insert(dependinfo.files, headerunit.path)
+ dependinfo.values = depvalues
+ depend.save(dependinfo, dependfile)
+ end)}
+ end
+end
+
+-- build headerunit file for batchcmds
+function make_headerunit_build_cmds(target, batchcmds, headerunit, bmifile, outputdir, opt)
+
+ batchcmds:mkdir(outputdir)
+
+ add_headerunit_to_target_mapper(target, headerunit, bmifile)
+
+ if opt.build then
+ local name = headerunit.unique and headerunit.name or headerunit.path
+ batchcmds:show_progress(opt.progress, "${color.build.target}<%s> ${clear}${color.build.object}compiling.headerunit.$(mode) %s", target:name(), name)
+ _batchcmds_compile(batchcmds, target, _make_headerunitflags(target, headerunit, bmifile, {batchcmds = true, bmifile = bmifile}))
+ end
+ batchcmds:add_depfiles(headerunit.path)
+ return os.mtime(bmifile)
+end
+
diff --git a/xmake/rules/c++/modules/modules_support/msvc/compiler_support.lua b/xmake/rules/c++/modules/modules_support/msvc/compiler_support.lua
new file mode 100644
index 000000000..3b6aafb9a
--- /dev/null
+++ b/xmake/rules/c++/modules/modules_support/msvc/compiler_support.lua
@@ -0,0 +1,239 @@
+--!A cross-platform build utility based on Lua
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-- Copyright (C) 2015-present, TBOOX Open Source Group.
+--
+-- @author ruki, Arthapz
+-- @file msvc/compiler_support.lua
+--
+
+-- imports
+import("core.base.semver")
+import("core.project.config")
+import("lib.detect.find_tool")
+import(".compiler_support", {inherit = true})
+
+-- load module support for the current target
+function load(target)
+
+ local msvc = target:toolchain("msvc")
+ local vcvars = msvc:config("vcvars")
+
+ -- enable std modules if c++23 by defaults
+ if target:data("c++.msvc.enable_std_import") == nil then
+ local languages = target:get("languages")
+ local isatleastcpp23 = false
+ for _, language in ipairs(languages) do
+ if language:startswith("c++") or language:startswith("cxx") then
+ isatleastcpp23 = true
+ local version = tonumber(language:match("%d+"))
+ if (not version or version <= 20) and not language:match("latest") then
+ isatleastcpp23 = false
+ break
+ end
+ end
+ end
+ local stdmodulesdir
+ local msvc = target:toolchain("msvc")
+ if msvc then
+ local vcvars = msvc:config("vcvars")
+ if vcvars.VCInstallDir and vcvars.VCToolsVersion and semver.compare(vcvars.VCToolsVersion, "14.35") > 0 then
+ stdmodulesdir = path.join(vcvars.VCInstallDir, "Tools", "MSVC", vcvars.VCToolsVersion, "modules")
+ end
+ end
+ target:data_set("c++.msvc.enable_std_import", isatleastcpp23 and os.isdir(stdmodulesdir))
+ end
+end
+
+-- provide toolchain include dir for stl headerunit when p1689 is not supported
+function toolchain_includedirs(target)
+ for _, toolchain_inst in ipairs(target:toolchains()) do
+ if toolchain_inst:name() == "msvc" then
+ local vcvars = toolchain_inst:config("vcvars")
+ if vcvars.VCInstallDir and vcvars.VCToolsVersion then
+ return { path.join(vcvars.VCInstallDir, "Tools", "MSVC", vcvars.VCToolsVersion, "include") }
+ end
+ break
+ end
+ end
+ raise("msvc toolchain includedirs not found!")
+end
+
+function get_stdmodules(target)
+ -- build c++23 standard modules if needed
+ if target:data("c++.msvc.enable_std_import") then
+ local msvc = target:toolchain("msvc")
+ if msvc then
+ local vcvars = msvc:config("vcvars")
+ if vcvars.VCInstallDir and vcvars.VCToolsVersion then
+ modules = {}
+
+ local stdmodulesdir = path.join(vcvars.VCInstallDir, "Tools", "MSVC", vcvars.VCToolsVersion, "modules")
+ assert(stdmodulesdir, "Can't enable C++23 std modules, directory missing !")
+
+ return {path.join(stdmodulesdir, "std.ixx"), path.join(stdmodulesdir, "std.compat.ixx")}
+ end
+ end
+ end
+end
+
+function get_bmi_extension()
+ return ".ifc"
+end
+
+function get_ifcoutputflag(target)
+ local ifcoutputflag = _g.ifcoutputflag
+ if ifcoutputflag == nil then
+ local compinst = target:compiler("cxx")
+ if compinst:has_flags({"-ifcOutput", os.tmpfile()}, "cxxflags", {flagskey = "cl_ifc_output"}) then
+ ifcoutputflag = "-ifcOutput"
+ end
+ assert(ifcoutputflag, "compiler(msvc): does not support c++ module flag(/ifcOutput)!")
+ _g.ifcoutputflag = ifcoutputflag or false
+ end
+ return ifcoutputflag or nil
+end
+
+function get_ifconlyflag(target)
+ local ifconlyflag = _g.ifconlyflag
+ if ifconlyflag == nil then
+ local compinst = target:compiler("cxx")
+ if compinst:has_flags({"-ifcOnly"}, "cxxflags", {flagskey = "cl_ifc_only"}) then
+ ifconlyflag = "-ifcOnly"
+ end
+ _g.ifconlyflag = ifconlyflag or false
+ end
+ return ifconlyflag or nil
+end
+
+function get_ifcsearchdirflag(target)
+ local ifcsearchdirflag = _g.ifcsearchdirflag
+ if ifcsearchdirflag == nil then
+ local compinst = target:compiler("cxx")
+ if compinst:has_flags({"-ifcSearchDir", os.tmpdir()}, "cxxflags", {flagskey = "cl_ifc_search_dir"}) then
+ ifcsearchdirflag = "-ifcSearchDir"
+ end
+ assert(ifcsearchdirflag, "compiler(msvc): does not support c++ module flag(/ifcSearchDir)!")
+ _g.ifcsearchdirflag = ifcsearchdirflag or false
+ end
+ return ifcsearchdirflag or nil
+end
+
+function get_interfaceflag(target)
+ local interfaceflag = _g.interfaceflag
+ if interfaceflag == nil then
+ local compinst = target:compiler("cxx")
+ if compinst:has_flags("-interface", "cxxflags", {flagskey = "cl_interface"}) then
+ interfaceflag = "-interface"
+ end
+ assert(interfaceflag, "compiler(msvc): does not support c++ module flag(/interface)!")
+ _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", "Foo=" .. os.tmpfile()}, "cxxflags", {flagskey = "cl_reference"}) then
+ referenceflag = "-reference"
+ end
+ assert(referenceflag, "compiler(msvc): does not support c++ module flag(/reference)!")
+ _g.referenceflag = referenceflag or false
+ end
+ return referenceflag or nil
+end
+
+function get_headernameflag(target)
+ local headernameflag = _g.headernameflag
+ if headernameflag == nil then
+ local compinst = target:compiler("cxx")
+ if compinst:has_flags({"-std:c++latest", "-exportHeader", "-headerName:quote"}, "cxxflags", {flagskey = "cl_header_name_quote"}) and
+ compinst:has_flags({"-std:c++latest", "-exportHeader", "-headerName:angle"}, "cxxflags", {flagskey = "cl_header_name_angle"}) then
+ headernameflag = "-headerName"
+ end
+ _g.headernameflag = headernameflag or false
+ end
+ return headernameflag or nil
+end
+
+function get_headerunitflag(target)
+ local headerunitflag = _g.headerunitflag
+ if headerunitflag == nil then
+ local compinst = target:compiler("cxx")
+ local ifcfile = os.tmpfile()
+ if compinst:has_flags({"-std:c++latest", "-headerUnit:quote", "foo.h=" .. ifcfile}, "cxxflags", {flagskey = "cl_header_unit_quote"}) and
+ compinst:has_flags({"-std:c++latest", "-headerUnit:angle", "foo.h=" .. ifcfile}, "cxxflags", {flagskey = "cl_header_unit_angle"}) then
+ headerunitflag = "-headerUnit"
+ end
+ _g.headerunitflag = headerunitflag or false
+ end
+ return headerunitflag or nil
+end
+
+function get_exportheaderflag(target)
+ local exportheaderflag = _g.exportheaderflag
+ if exportheaderflag == nil then
+ if get_headernameflag(target) then
+ exportheaderflag = "-exportHeader"
+ end
+ _g.exportheaderflag = exportheaderflag or false
+ end
+ return exportheaderflag or nil
+end
+
+function get_scandependenciesflag(target)
+ local scandependenciesflag = _g.scandependenciesflag
+ if scandependenciesflag == nil then
+ local compinst = target:compiler("cxx")
+ local scan_dependencies_jsonfile = os.tmpfile() .. ".json"
+ if compinst:has_flags("-scanDependencies " .. scan_dependencies_jsonfile, "cxflags", {flagskey = "cl_scan_dependencies",
+ on_check = function (ok, errors)
+ if os.isfile(scan_dependencies_jsonfile) then
+ ok = true
+ end
+ if ok and not os.isfile(scan_dependencies_jsonfile) then
+ ok = false
+ end
+ return ok, errors
+ end}) then
+ scandependenciesflag = "-scanDependencies"
+ end
+ _g.scandependenciesflag = scandependenciesflag or false
+ end
+ return scandependenciesflag or nil
+end
+
+function get_cppversionflag(target)
+ local cppversionflag = _g.cppversionflag
+ if cppversionflag == nil then
+ local compinst = target:compiler("cxx")
+ local flags = compinst:compflags({target = target})
+ cppversionflag = table.find_if(flags, function(v) string.startswith(v, "/std:c++") end) or "/std:c++latest"
+ end
+ return cppversionflag or nil
+end
+
+function get_internalpartitionflag(target)
+ local internalpartitionflag = _g.internalpartitionflag
+ if internalpartitionflag == nil then
+ local compinst = target:compiler("cxx")
+ if compinst:has_flags("-internalPartition", "cxxflags", {flagskey = "cl_internal_partition"}) then
+ internalpartitionflag = "-internalPartition"
+ end
+ _g.internalpartitionflag = internalpartitionflag or false
+ end
+ return internalpartitionflag or nil
+end
diff --git a/xmake/rules/c++/modules/modules_support/msvc/dependency_scanner.lua b/xmake/rules/c++/modules/modules_support/msvc/dependency_scanner.lua
new file mode 100644
index 000000000..ed5027e22
--- /dev/null
+++ b/xmake/rules/c++/modules/modules_support/msvc/dependency_scanner.lua
@@ -0,0 +1,72 @@
+--!A cross-platform build utility based on Lua
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-- Copyright (C) 2015-present, TBOOX Open Source Group.
+--
+-- @author ruki, Arthapz
+-- @file msvc/dependency_scanner.lua
+--
+
+-- imports
+import("core.base.json")
+import("core.base.semver")
+import("core.project.depend")
+import("private.tools.vstool")
+import("utils.progress")
+import("compiler_support")
+import("builder")
+import(".dependency_scanner", {inherit = true})
+
+-- generate dependency files
+function generate_dependencies(target, sourcebatch, opt)
+ local msvc = target:toolchain("msvc")
+ local scandependenciesflag = compiler_support.get_scandependenciesflag(target)
+ local ifcoutputflag = compiler_support.get_ifcoutputflag(target)
+ local common_flags = {"-TP", scandependenciesflag}
+ local changed = false
+
+ for _, sourcefile in ipairs(sourcebatch.sourcefiles) do
+ local dependfile = target:dependfile(sourcefile)
+ depend.on_changed(function ()
+ progress.show(opt.progress, "${color.build.target}<%s> generating.module.deps %s", target:name(), sourcefile)
+ local outputdir = compiler_support.get_outputdir(target, sourcefile)
+
+ local jsonfile = path.join(outputdir, path.filename(sourcefile) .. ".module.json")
+ if scandependenciesflag and not target:policy("build.c++.msvc.fallbackscanner") then
+ local flags = {jsonfile, sourcefile, ifcoutputflag, outputdir, "-Fo" .. target:objectfile(sourcefile)}
+ local compinst = target:compiler("cxx")
+ local msvc = target:toolchain("msvc")
+ local compflags = table.join(compinst:compflags({sourcefile = sourcefile, target = target}) or {}, common_flags, flags)
+ os.vrunv(compinst:program(), winos.cmdargv(compflags), {envs = msvc:runenvs()})
+ else
+ fallback_generate_dependencies(target, jsonfile, sourcefile, function(file)
+ local compinst = target:compiler("cxx")
+ local compflags = compinst:compflags({sourcefile = file, target = target})
+ local ifile = path.translate(path.join(outputdir, path.filename(file) .. ".i"))
+ os.vrunv(compinst:program(), table.join(compflags,
+ {"/P", "-TP", file, "/Fi" .. ifile}), {envs = msvc:runenvs()})
+ local content = io.readfile(ifile)
+ os.rm(ifile)
+ return content
+ end)
+ end
+ changed = true
+
+ local dependinfo = io.readfile(jsonfile)
+ return { moduleinfo = dependinfo }
+ end, {dependfile = dependfile, files = {sourcefile}, changed = target:is_rebuilt()})
+ end
+ return changed
+end
+