summaryrefslogtreecommitdiff
path: root/xmake/rules/c++/modules/modules_support/msvc
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/msvc
parentda5f589a7ea6dfce340b92aad88db8b84aebd5bd (diff)
refactor msvc module infrastructure
Diffstat (limited to 'xmake/rules/c++/modules/modules_support/msvc')
-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
3 files changed, 633 insertions, 0 deletions
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
+