--!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, Xmake Open Source Community. -- -- @author ruki -- @file vs201x_vcxproj.lua -- -- imports import("core.base.hashset") import("core.project.rule") import("core.project.config") import("core.project.project") import("core.language.language") import("core.tool.toolchain") import("private.utils.batchcmds") import("detect.sdks.find_cuda") import("vsfile") import("vsutils") import("private.utils.toolchain", {alias = "toolchain_utils"}) import("rules.c++.modules.support", {rootdir = os.programdir()}) function _make_dirs(dir, vcxprojdir) dir = dir:trim() if #dir == 0 then return "" end dir = path.translate(dir) if not path.is_absolute(dir) then dir = path.relative(path.absolute(dir), vcxprojdir) end return dir end -- check for CUDA function _check_cuda(target) local cuda for _, targetinfo in ipairs(target.info) do if targetinfo.sourcekinds and table.contains(targetinfo.sourcekinds, "cu") then cuda = find_cuda() break end end if cuda then if cuda.msbuildextensionsdir and cuda.version and os.isfile(path.join(cuda.msbuildextensionsdir, format("CUDA %s.props", cuda.version))) then return cuda else os.raise("The Visual Studio Integration for CUDA %s is not found. Please check your CUDA installation.", cuda.version) end end end -- get toolset version function _get_toolset_ver(targetinfo, vsinfo) -- get toolset version from vs version local vs_toolset = toolchain.load("msvc"):config("vs_toolset") or config.get("vs_toolset") local toolset_ver = toolchain_utils.get_vs_toolset_ver(vs_toolset) if not toolset_ver then toolset_ver = vsinfo.toolset_version end return toolset_ver end -- get platform sdk version from vcvars.WindowsSDKVersion function _get_platform_sdkver(target, vsinfo) local sdkver = nil for _, targetinfo in ipairs(target.info) do sdkver = targetinfo.sdkver if sdkver then break end end return sdkver or vsinfo.sdk_version end -- combine two successive flags function _combine_flags(flags, patterns) local newflags = {} local temparg for _, arg in ipairs(flags) do if temparg then table.insert(newflags, temparg .. " " .. arg) temparg = nil else for _, pattern in ipairs(patterns) do if arg:match(pattern) then temparg = arg end end if not temparg then table.insert(newflags, arg) end end end return newflags end -- exclude patterns from flags function _exclude_flags(flags, excludes) local newflags = {} for _, flag in ipairs(flags) do local excluded = false for _, exclude in ipairs(excludes) do if flag:find("^[%-/]" .. exclude) then excluded = true break end end if not excluded then table.insert(newflags, vsutils.escape(flag)) end end return newflags end -- try split from nvcc -code flag -- e.g. nvcc -arch=compute_86 -code=\"sm_86,compute_86\" -- nvcc -gencode arch=compute_86,code=[sm_86,compute_86] function _split_gpucodes(flag) flag = flag:gsub("[%[\"]?(.-)[%]\"]?", "%1") return flag:split(",") end -- make compiling command function _make_compcmd(compargv, sourcefile, objectfile, vcxprojdir) local argv = {} for i, v in ipairs(compargv) do if i == 1 then v = path.filename(v) -- C:\xxx\ml.exe -> ml.exe end v = v:gsub("__sourcefile__", sourcefile) v = v:gsub("__objectfile__", objectfile) -- -Idir or /Idir -- handle external includes as well for _, pattern in ipairs({"[%-/](I)(.*)", "[%-/](external:I)(.*)"}) do v = v:gsub(pattern, function (flag, dir) dir = _make_dirs(dir, vcxprojdir) return "/" .. flag .. dir end) end table.insert(argv, v) end return table.concat(argv, " ") end -- make compiling flags function _make_compflags(sourcefile, targetinfo, vcxprojdir) -- translate path for -Idir or /Idir local flags = {} for _, flag in ipairs(targetinfo.compflags[sourcefile]) do for _, pattern in ipairs({"[%-/](I)(.*)", "[%-/](external:I)(.*)"}) do -- -Idir or /Idir flag = flag:gsub(pattern, function (flag, dir) dir = _make_dirs(dir, vcxprojdir) return "/" .. flag .. dir end) end table.insert(flags, flag) end -- add -D__config_$(mode)__ and -D__config_$(arch)__ for the config header table.insert(flags, "-D__config_" .. targetinfo.mode .. "__") table.insert(flags, "-D__config_" .. targetinfo.arch .. "__") return flags end -- make linking flags function _make_linkflags(targetinfo, vcxprojdir) -- replace -libpath:dir or /libpath:dir local flags = {} for _, flag in ipairs(targetinfo.linkflags) do -- replace -libpath:dir or /libpath:dir flag = flag:gsub(string.ipattern("[%-/]libpath:(.*)"), function (dir) dir = _make_dirs(dir, vcxprojdir) return "/libpath:" .. dir end) -- replace -def:dir or /def:dir flag = flag:gsub(string.ipattern("[%-/]def:(.*)"), function (dir) dir = _make_dirs(dir, vcxprojdir) return "/def:" .. dir end) -- save flag table.insert(flags, flag) end -- ok? return flags end -- make header function _make_header(vcxprojfile, vsinfo) vcxprojfile:print("") vcxprojfile:enter("", assert(vsinfo.project_version)) end -- make references function _make_references(vcxprojfile, vsinfo, target) vcxprojfile:print("") for dep_name, dep_vcxprojfile in pairs(target.deps) do vcxprojfile:print("", dep_vcxprojfile) vcxprojfile:print("{%s}", hash.uuid4(dep_name)) vcxprojfile:print("") end vcxprojfile:print("") end -- make tailer function _make_tailer(vcxprojfile, vsinfo, target) vcxprojfile:print("") vcxprojfile:enter("") local cuda = _check_cuda(target) if cuda then vcxprojfile:print("", path.join(cuda.msbuildextensionsdir, format("CUDA %s.targets", cuda.version))) end vcxprojfile:leave("") vcxprojfile:leave("") end -- make Configurations function _make_configurations(vcxprojfile, vsinfo, target) -- the target name local targetname = target.name -- init configuration type local configuration_types = { binary = "Application" , shared = "DynamicLibrary" , static = "StaticLibrary" , moduleonly = "StaticLibrary" -- emulate moduleonly with staticlib } -- make ProjectConfigurations vcxprojfile:enter("") for _, targetinfo in ipairs(target.info) do vcxprojfile:enter("", targetinfo.mode, targetinfo.arch) vcxprojfile:print("%s", targetinfo.mode) vcxprojfile:print("%s", targetinfo.arch) vcxprojfile:leave("") end vcxprojfile:leave("") -- make Globals vcxprojfile:enter("") vcxprojfile:print("{%s}", hash.uuid4(targetname)) vcxprojfile:print("%s", targetname) if vsinfo.vstudio_version >= "2015" then vcxprojfile:print("%s", _get_platform_sdkver(target, vsinfo)) end vcxprojfile:leave("") -- import Microsoft.Cpp.Default.props vcxprojfile:print("") -- make Configuration for _, targetinfo in ipairs(target.info) do vcxprojfile:enter("", targetinfo.mode, targetinfo.arch) vcxprojfile:print("%s", configuration_types[target.kind] or "Unknown") vcxprojfile:print("%s", _get_toolset_ver(targetinfo, vsinfo)) vcxprojfile:print("%s", targetinfo.unicode and "Unicode" or "MultiByte") if targetinfo.usemfc then vcxprojfile:print("%s", targetinfo.usemfc) end vcxprojfile:leave("") end -- import Microsoft.Cpp.props vcxprojfile:print("") -- make ExtensionSettings vcxprojfile:enter("") local cuda = _check_cuda(target) if cuda then vcxprojfile:print("", path.join(cuda.msbuildextensionsdir, format("CUDA %s.props", cuda.version))) end vcxprojfile:leave("") -- make PropertySheets for _, targetinfo in ipairs(target.info) do vcxprojfile:enter("", targetinfo.mode, targetinfo.arch) vcxprojfile:print("") vcxprojfile:leave("") end -- make UserMacros vcxprojfile:print("") if not configuration_types[target.kind] then return end -- make OutputDirectory and IntermediateDirectory for _, targetinfo in ipairs(target.info) do vcxprojfile:enter("", targetinfo.mode, targetinfo.arch) vcxprojfile:print("%s\\", _make_dirs(targetinfo.targetdir, target.project_dir)) vcxprojfile:print("%s\\", _make_dirs(targetinfo.objectdir, target.project_dir)) if targetinfo.targetfile then vcxprojfile:print("%s", path.basename(targetinfo.targetfile)) vcxprojfile:print("%s", path.extension(targetinfo.targetfile)) end if target.kind == "binary" then vcxprojfile:print("true") end if targetinfo.manifest_embed ~= nil then vcxprojfile:print("%s", targetinfo.manifest_embed) end -- handle ExternalIncludePath (should we handle IncludePath here too?) local externaldirs = {} for _, flag in ipairs(targetinfo.commonflags.cl) do flag:gsub("[%-/]external:I(.*)", function (dir) table.insert(externaldirs, dir) end) end if #externaldirs > 0 then vcxprojfile:print("%s;$(VC_IncludePath);$(WindowsSDK_IncludePath);", table.concat(externaldirs, ";")) end vcxprojfile:leave("") end -- make Debugger for _, targetinfo in ipairs(target.info) do vcxprojfile:enter("", targetinfo.mode, targetinfo.arch) vcxprojfile:print("%s", _make_dirs(targetinfo.rundir, target.project_dir)) -- @note we use writef to avoid escape $() in runenvs, e.g. $([System.Environment]::Get ..) vcxprojfile:writef("%s;%%(LocalDebuggerEnvironment)\n", targetinfo.runenvs) vcxprojfile:leave("") end end -- make source options for cl function _make_source_options_cl(vcxprojfile, flags, condition) -- exists condition? condition = condition or "" -- get flags string local flagstr = os.args(flags) -- make Optimization if flagstr:find("[%-/]Os") or flagstr:find("[%-/]O1") then vcxprojfile:print("MinSpace", condition) elseif flagstr:find("[%-/]O2") or flagstr:find("[%-/]Ot") then vcxprojfile:print("MaxSpeed", condition) elseif flagstr:find("[%-/]Ox") then vcxprojfile:print("Full", condition) else vcxprojfile:print("Disabled", condition) end -- make FloatingPointModel if flagstr:find("[%-/]fp:fast") then vcxprojfile:print("Fast", condition) elseif flagstr:find("[%-/]fp:strict") then vcxprojfile:print("Strict", condition) elseif flagstr:find("[%-/]fp:precise") then vcxprojfile:print("Precise", condition) end -- make WarningLevel if flagstr:find("[%-/]W1") then vcxprojfile:print("Level1", condition) elseif flagstr:find("[%-/]W2") then vcxprojfile:print("Level2", condition) elseif flagstr:find("[%-/]W3") then vcxprojfile:print("Level3", condition) elseif flagstr:find("[%-/]W4") then vcxprojfile:print("Level4", condition) elseif flagstr:find("[%-/]Wall") then vcxprojfile:print("EnableAllWarnings", condition) else vcxprojfile:print("TurnOffAllWarnings", condition) end if flagstr:find("[%-/]WX") then vcxprojfile:print("true", condition) end -- make ExternalWarningLevel if flagstr:find("[%-/]external:W1") then vcxprojfile:print("Level1", condition) elseif flagstr:find("[%-/]external:W2") then vcxprojfile:print("Level2", condition) elseif flagstr:find("[%-/]external:W3") then vcxprojfile:print("Level3", condition) elseif flagstr:find("[%-/]external:W4") then vcxprojfile:print("Level4", condition) else vcxprojfile:print("TurnOffAllWarnings", condition) end -- make ExternalTemplatesDiagnostics if flagstr:find("[%-/]external:templates%-") then vcxprojfile:print("true", condition) else vcxprojfile:print("false", condition) end -- make DisableSpecificWarnings local disabledwarnings = {} for _, flag in ipairs(flags) do flag:gsub("[%-/]wd(%d+)", function (warn) table.insert(disabledwarnings, warn) end) end if #disabledwarnings > 0 then vcxprojfile:print("%s;%%(DisableSpecificWarnings)", table.concat(disabledwarnings, ";")) end -- make PreprocessorDefinitions local defstr = "" for _, flag in ipairs(flags) do flag:gsub("^[%-/]D(.*)", function (def) defstr = defstr .. vsutils.escape(def) .. ";" end ) end defstr = defstr .. "%%(PreprocessorDefinitions)" vcxprojfile:print("%s", condition, defstr) -- make DebugInformationFormat if flagstr:find("[%-/]Zi") then vcxprojfile:print("ProgramDatabase", condition) elseif flagstr:find("[%-/]ZI") then vcxprojfile:print("EditAndContinue", condition) elseif flagstr:find("[%-/]Z7") then vcxprojfile:print("OldStyle", condition) else vcxprojfile:print("None", condition) end -- make RuntimeLibrary if flagstr:find("[%-/]MDd") then vcxprojfile:print("MultiThreadedDebugDLL", condition) elseif flagstr:find("[%-/]MD") then vcxprojfile:print("MultiThreadedDLL", condition) elseif flagstr:find("[%-/]MTd") then vcxprojfile:print("MultiThreadedDebug", condition) else vcxprojfile:print("MultiThreaded", condition) end -- make RuntimeTypeInfo if flagstr:find("[%-/]GR%-") then vcxprojfile:print("false", condition) elseif flagstr:find("[%-/]GR") then vcxprojfile:print("true", condition) end -- handle multi processor compilation if flagstr:find("[%-/]Gm%-") or not flagstr:find("[%-/]Gm") then vcxprojfile:print("false", condition) if not flagstr:find("[%-/]MP1") then vcxprojfile:print("true", condition) end end -- make AdditionalIncludeDirectories if flagstr:find("[%-/]I") then local dirs = {} for _, flag in ipairs(flags) do flag:gsub("^[%-/]I(.*)", function (dir) table.insert(dirs, vsutils.escape(dir)) end) end if #dirs > 0 then vcxprojfile:print("%s", condition, table.concat(dirs, ";")) end end -- compile as c++ if exists flag: /TP if flagstr:find("[%-/]TP") then vcxprojfile:print("CompileAsCpp", condition) end -- make SDLCheck flag: /sdl if flagstr:find("[%-/]sdl") then if flagstr:find("[%-/]sdl%-") then vcxprojfile:print("false", condition) else vcxprojfile:print("true", condition) end end -- make RemoveUnreferencedCodeData flag: Zc:inline if flagstr:find("[%-/]Zc:inline") then if flagstr:find("[%-/]Zc:inline%-") then vcxprojfile:print("false", condition) else vcxprojfile:print("true", condition) end end -- make ExceptionHandling flag: if flagstr:find("[%-/]EH[asc]+%-?") then local args = flagstr:match("[%-/]EH([asc]+%-?)") -- remove the last arg if flag endwith `-` if args and args:endswith("-") then args = args:sub(1, -2) end if args and args:find("a", 1, true) then -- a will overwrite s and c vcxprojfile:print("Async", condition) elseif args == "sc" or args == "cs" then vcxprojfile:print("Sync", condition) elseif args == "s" then vcxprojfile:print("SyncCThrow", condition) else -- if args == "c" -- c is ignored without s or a, do nothing here end end -- make AdditionalOptions local excludes = { "Od", "Os", "O0", "O1", "O2", "Ot", "Ox", "W0", "W1", "W2", "W3", "W4", "WX", "Wall", "Zi", "ZI", "Z7", "MT", "MTd", "MD", "MDd", "TP", "Fd", "fp", "I", "D", "Gm%-", "Gm", "GR%-", "GR", "MP", "external:W0", "external:W1", "external:W2", "external:W3", "external:W4", "external:templates%-?", "external:I", "std:c11", "std:c17", "std:c%+%+11", "std:c%+%+14", "std:c%+%+17", "std:c%+%+20", "std:c%+%+latest", "nologo", "wd(%d+)", "sdl%-?", "Zc:inline%-?", "EH[asc]+%-?" } local additional_flags = _exclude_flags(flags, excludes) if #additional_flags > 0 then vcxprojfile:print("%s %%(AdditionalOptions)", condition, os.args(additional_flags)) end end -- make source options for cl function _make_resource_options_cl(vcxprojfile, flags) -- get flags string local flagstr = os.args(flags) -- make PreprocessorDefinitions local defstr = "" for _, flag in ipairs(flags) do flag:gsub("^[%-/]D(.*)", function (def) defstr = defstr .. vsutils.escape(def) .. ";" end ) end defstr = defstr .. "%%(PreprocessorDefinitions)" vcxprojfile:print("%s", defstr) -- make AdditionalIncludeDirectories if flagstr:find("[%-/]I") then local dirs = {} for _, flag in ipairs(flags) do flag:gsub("^[%-/]I(.*)", function (dir) table.insert(dirs, vsutils.escape(dir)) end) end if #dirs > 0 then vcxprojfile:print("%s", table.concat(dirs, ";")) end end end -- make source options for cuda function _make_source_options_cuda(vcxprojfile, flags, opt) -- exists condition? condition = (opt and opt.condition) or "" -- combine successive commands flags = _combine_flags(flags, {"^%-gencode$", "^%-arch$", "^%-code$", "^%-%-machine$", "^%-rdc$", "^%-cudart$", "^%-%-keep%-dir$"}) -- get flags string local flagstr = os.args(flags) if not (opt and opt.link) then -- make Optimization if flagstr:find("[%-/]Od") then vcxprojfile:print("Od", condition) elseif flagstr:find("[%-/]O1") then vcxprojfile:print("O1", condition) elseif flagstr:find("[%-/]O2") then vcxprojfile:print("O2", condition) elseif flagstr:find("[%-/]O3") or flagstr:find("[%-/]Ox") then vcxprojfile:print("O3", condition) end -- make Warning if flagstr:find("[%-/]W[1234]") then local wlevel = flagstr:match("[%-/](W[1234])") vcxprojfile:print("%s", condition, wlevel) elseif flagstr:find("[%-/]Wall") then vcxprojfile:print("Wall", condition) end -- make Defines local defstr = "" for _, flag in ipairs(flags) do flag:gsub("^[%-/]D(.*)", function (def) defstr = defstr .. vsutils.escape(def) .. ";" end ) end defstr = defstr .. "%%(Defines)" vcxprojfile:print("%s", condition, defstr) -- make Include if flagstr:find("[%-/]I") then local dirs = {} for _, flag in ipairs(flags) do flag:gsub("^[%-/]I(.*)", function (dir) table.insert(dirs, vsutils.escape(dir)) end) end if #dirs > 0 then vcxprojfile:print("%s", condition, table.concat(dirs, ";")) end end end -- make TargetMachinePlatform local machinebitwidth for _, flag in ipairs(flags) do flag:gsub("^%-m(.+)", function (value) machinebitwidth = value end) flag:gsub("^%-%-machine[ =](.+)", function (value) machinebitwidth = value end) end if machinebitwidth and (machinebitwidth == "32" or machinebitwidth == "64") then vcxprojfile:print("%s", condition, machinebitwidth) end -- make CodeGeneration local gpucode_patterns = { "%-gencode[ =]arch=(.+),code=(.+)", "%-%-generate%-code[ =]arch=(.+),code=(.+)", "%-arch", "%-%-gpu%-architecture", "%-code", "%-%-gpu%-code" } local has_gpucode = false for _, pattern in ipairs(gpucode_patterns) do if flagstr:find(pattern) then has_gpucode = true break end end if has_gpucode then local arch local codes = {} local gencodes = {} for _, flag in ipairs(flags) do flag:gsub("^%-gencode[ =]arch=(.+),code=(.+)$", function (garch, gcodes) for _, gcode in ipairs(_split_gpucodes(gcodes)) do table.insert(gencodes, garch .. "," .. gcode) end end) flag:gsub("^%-%-generate%-code[ =]arch=(.+),code=(.+)", function (garch, gcodes) for _, gcode in ipairs(_split_gpucodes(gcodes)) do table.insert(gencodes, garch .. "," .. gcode) end end) flag:gsub("^%-arch[ =](.+)", function (garch) arch = garch end) flag:gsub("^%-%-gpu%-architecture[ =](.+)", function (garch) arch = garch end) flag:gsub("^%-code[ =](.+)", function (gcodes) table.join2(codes, _split_gpucodes(gcodes)) end) flag:gsub("^%-%-gpu%-code[ =](.+)", function (gcodes) table.join2(codes, _split_gpucodes(gcodes)) end) end if arch then if #codes == 0 then table.insert(codes, arch) arch = arch:gsub("sm", "compute") table.insert(gencodes, arch .. "," .. arch) end for _, code in ipairs(codes) do table.insert(gencodes, arch .. "," .. code) end end if #gencodes > 0 then gencodes = table.unique(gencodes) vcxprojfile:print("%s", condition, table.concat(gencodes, ";")) end end if not (opt and opt.link) then -- make CudaRuntime local cudart local cudaruntime = { none = "None", static = "Static", shared = "Shared" } for _, flag in ipairs(flags) do flag:gsub("%-cudart[ =](.+)", function (value) cudart = value end) end if cudart and cudaruntime[cudart] then vcxprojfile:print("%s", condition, cudaruntime[cudart]) end -- handle GPU debug info if flagstr:find("%-G") then vcxprojfile:print("true", condition) end -- handle fast math if flagstr:find("%-use_fast_math") then vcxprojfile:print("true", condition) end -- handle relocatable device code local rdc for _, flag in ipairs(flags) do flag:gsub("%-rdc[ =](.+)", function (value) rdc = value end) end if rdc then vcxprojfile:print("%s", condition, rdc) end -- handle keep preprocessed files or directories if flagstr:find("%-%-keep") then vcxprojfile:print("true", condition) end if flagstr:find("%-%-keep%-dir") then local dirs = {} for _, flag in ipairs(flags) do flag:gsub("%-%-keep%-dir[ =](.*)", function (dir) table.insert(dirs, dir) end) end if #dirs > 0 then vcxprojfile:print("%s", condition, table.concat(dirs, ";")) end end end -- make AdditionalOptions local excludes = { "Od", "O1", "O2", "O3", "Ox", "W1", "W2", "W3", "W4", "Wall", "I", "D", "L", "l", "m", "%-machine", "gencode", "arch", "code", "cudart", "G", "use_fast_math", "rdc", "%-keep", "%-keep%-dir" } local additional_flags = _exclude_flags(flags, excludes) if #additional_flags > 0 then vcxprojfile:print("%s %%(AdditionalOptions)", condition, os.args(additional_flags)) end end -- make custom commands item function _make_custom_commands_item(vcxprojfile, commands, suffix) if suffix == "after" or suffix == "after_link" then vcxprojfile:print("") elseif suffix == "before" then vcxprojfile:print("") elseif suffix == "before_link" then vcxprojfile:print("") end vcxprojfile:print("") local cmdstr = "setlocal" for _, command in ipairs(commands) do cmdstr = cmdstr .. "\n" .. command cmdstr = cmdstr .. "\nif %errorlevel% neq 0 goto :xmEnd" end cmdstr = cmdstr .. "\n" .. [[:xmEnd endlocal & call :xmErrorLevel %errorlevel% & goto :xmDone :xmErrorLevel exit /b %1 :xmDone if %errorlevel% neq 0 goto :VCEnd]] vcxprojfile:print("%s", cmdstr:replace("<", " <"):replace(">", ">"):replace("/Fo ", "/Fo")) if suffix == "after" or suffix == "after_link" then vcxprojfile:print("") elseif suffix == "before" then vcxprojfile:print("") elseif suffix == "before_link" then vcxprojfile:print("") end end -- make custom commands function _make_custom_commands(vcxprojfile, target) for suffix, cmds in pairs(target.commands) do _make_custom_commands_item(vcxprojfile, cmds, suffix) end end -- make common item function _make_common_item(vcxprojfile, vsinfo, target, targetinfo) -- init the linker kinds local linkerkinds = { binary = "Link" , static = "Lib" , moduleonly = "Lib" -- emulate moduleonly with staticlib , shared = "Link" } if not linkerkinds[targetinfo.targetkind] then return end -- enter ItemDefinitionGroup vcxprojfile:enter("", targetinfo.mode, targetinfo.arch) -- for linker? vcxprojfile:enter("<%s>", linkerkinds[targetinfo.targetkind]) -- save subsystem local subsystem = "Console" -- save profile local profile = false -- make linker flags local flags = {} local excludes = { "nologo", "machine:%w+", "pdb:.+%.pdb", "debug" } local libdirs = {} local links = {} for _, flag in ipairs(_make_linkflags(targetinfo, target.project_dir)) do local flag_lower = flag:lower() -- remove "-subsystem:windows" if flag_lower:find("[%-/]subsystem:windows") then subsystem = "Windows" elseif flag_lower:find("[%-/]libpath") then -- link dir flag:gsub("[%-/]libpath:(.*)", function (dir) table.insert(libdirs, vsutils.escape(dir)) end) elseif flag_lower:find("[^%-/].+%.lib") then -- link file table.insert(links, flag) elseif flag_lower:find("[%-/]profile") then profile = true else local excluded = false for _, exclude in ipairs(excludes) do if flag:find("[%-/]" .. exclude) then excluded = true break end end if not excluded then table.insert(flags, flag) end end end -- make AdditionalLibraryDirectories if #libdirs > 0 then vcxprojfile:print("%s;%%(AdditionalLibraryDirectories)", table.concat(libdirs, ";")) end -- make AdditionalDependencies if #links > 0 then vcxprojfile:print("%s;%%(AdditionalDependencies)", table.concat(links, ";")) end -- make AdditionalOptions if #flags > 0 then flags = os.args(flags) vcxprojfile:print("%s %%(AdditionalOptions)", vsutils.escape(flags)) end -- generate debug infomation? if linkerkinds[targetinfo.targetkind] == "Link" then -- enable profile? vcxprojfile:print("%s", tostring(profile)) -- enable debug infomation? local debug = false for _, symbol in ipairs(targetinfo.symbols) do if symbol == "debug" then debug = true break end end vcxprojfile:print("%s", tostring(debug)) end -- make SubSystem if targetinfo.targetkind == "binary" then vcxprojfile:print("%s", subsystem) end -- make TargetMachine vcxprojfile:print("%s", (targetinfo.arch == "x64" and "MachineX64" or "MachineX86")) vcxprojfile:leave("", linkerkinds[targetinfo.targetkind]) -- for C/C++ compiler? vcxprojfile:enter("") -- make source options _make_source_options_cl(vcxprojfile, targetinfo.commonflags.cl) -- add c and c++ standard local clangflags = { c11 = "stdc11", c17 = "stdc17", clatest = "stdc17", gnu11 = "stdc11", gnu17 = "stdc17", gnulatest = "stdc17", } local cxxlangflags = { cxx11 = "stdcpp11", cxx14 = "stdcpp14", cxx17 = "stdcpp17", cxx1z = "stdcpp17", cxx20 = "stdcpp20", cxx2a = "stdcpplatest", cxx23 = "stdcpplatest", cxx2b = "stdcpplatest", cxxlatest = "stdcpplatest", gnuxx11 = "stdcpp11", gnuxx14 = "stdcpp14", gnuxx17 = "stdcpp17", gnuxx1z = "stdcpp20", gnux20 = "stdcpp20", gnux2a = "stdcpplatest", } local cstandard local cxxstandard for _, lang in pairs(targetinfo.languages) do lang = lang:replace("c++", "cxx", {plain = true}) if cxxlangflags[lang] then cxxstandard = cxxlangflags[lang] elseif clangflags[lang] then cstandard = clangflags[lang] end end if cxxstandard then vcxprojfile:print("%s", cxxstandard) end if cstandard then vcxprojfile:print("%s", cstandard) end if targetinfo.has_modules then vcxprojfile:enter("true") end -- use c or c++ precompiled header local pcheader = target.pcxxheader or target.pcheader if pcheader then -- make precompiled header and outputfile vcxprojfile:print("Use") vcxprojfile:print("%s", vsutils.escape(path.filename(pcheader))) local pcoutputfile = targetinfo.pcxxoutputfile or targetinfo.pcoutputfile if pcoutputfile then vcxprojfile:print("%s", vsutils.escape(path.relative(path.absolute(pcoutputfile), target.project_dir))) end vcxprojfile:print("%s;%%(ForcedIncludeFiles)", vsutils.escape(path.filename(pcheader))) end vcxprojfile:leave("") vcxprojfile:enter("") -- make resource options _make_resource_options_cl(vcxprojfile, targetinfo.commonflags.cl) vcxprojfile:leave("") local cuda = _check_cuda(target) if cuda then -- for CUDA linker? vcxprojfile:enter("") -- make cuda link flags _make_source_options_cuda(vcxprojfile, targetinfo.culinkflags, {link = true}) -- make devlink if targetinfo.cudevlink then vcxprojfile:print("%s", targetinfo.cudevlink) end vcxprojfile:leave("") -- for CUDA compiler? vcxprojfile:enter("") -- make source options _make_source_options_cuda(vcxprojfile, targetinfo.commonflags.cuda) vcxprojfile:leave("") end -- make custom commands _make_custom_commands(vcxprojfile, targetinfo) -- leave ItemDefinitionGroup vcxprojfile:leave("") end -- build common items (doesn't print anything) function _build_common_items(vsinfo, target) -- for each mode and arch for _, targetinfo in ipairs(target.info) do -- make source flags local flags_stats = {cl = {}, cuda = {}} local files_count = {cl = 0, cuda = 0} local first_flags = {} targetinfo.sourceflags = {} for _, sourcebatch in pairs(targetinfo.sourcebatches) do local sourcekind = sourcebatch.sourcekind local rulename = sourcebatch.rulename if (rulename == "c.build" or rulename == "c++.build" or rulename == "c++.build.modules" or rulename == "asm.build" or sourcekind == "mrc") then for _, sourcefile in ipairs(sourcebatch.sourcefiles) do -- make compiler flags local flags = _make_compflags(sourcefile, targetinfo, target.project_dir) -- no common flags for asm/rc if sourcekind ~= "as" and sourcekind ~= "mrc" then for _, flag in ipairs(table.unique(flags)) do flags_stats.cl[flag] = (flags_stats.cl[flag] or 0) + 1 end -- update files count files_count.cl = files_count.cl + 1 -- save first flags if first_flags.cl == nil then first_flags.cl = flags end end -- save source flags targetinfo.sourceflags[sourcefile] = flags end elseif sourcekind == "cu" then for _, sourcefile in ipairs(sourcebatch.sourcefiles) do -- make compiler flags local flags = _make_compflags(sourcefile, targetinfo, target.project_dir) -- count flags for _, flag in ipairs(table.unique(flags)) do flags_stats.cuda[flag] = (flags_stats.cuda[flag] or 0) + 1 end -- update files count files_count.cuda = files_count.cuda + 1 -- save first flags if first_flags.cuda == nil then first_flags.cuda = flags end -- save source flags targetinfo.sourceflags[sourcefile] = flags end end end -- make common flags targetinfo.commonflags = {cl = {}, cuda = {}} for _, comp in ipairs({"cl", "cuda"}) do for _, flag in ipairs(first_flags[comp]) do if flags_stats[comp][flag] >= files_count[comp] then table.insert(targetinfo.commonflags[comp], flag) end end end -- remove common flags from source flags local sourceflags = {} for _, sourcebatch in pairs(targetinfo.sourcebatches) do local sourcekind = sourcebatch.sourcekind local rulename = sourcebatch.rulename if (sourcekind == "as" or sourcekind == "mrc") then -- no common flags for as/mrc files for _, sourcefile in ipairs(sourcebatch.sourcefiles) do sourceflags[sourcefile] = targetinfo.sourceflags[sourcefile] end elseif rulename == "c.build" or rulename == "c++.build" or rulename == "c++.build.modules" then -- sourcekind maybe bind multiple rules, e.g. c++modules for _, sourcefile in ipairs(sourcebatch.sourcefiles) do local flags = targetinfo.sourceflags[sourcefile] local otherflags = {} for _, flag in ipairs(flags) do if flags_stats.cl[flag] < files_count.cl then table.insert(otherflags, flag) end end sourceflags[sourcefile] = otherflags end elseif sourcekind == "cu" then for _, sourcefile in ipairs(sourcebatch.sourcefiles) do local flags = targetinfo.sourceflags[sourcefile] local otherflags = {} for _, flag in ipairs(flags) do if flags_stats.cuda[flag] < files_count.cuda then table.insert(otherflags, flag) end end sourceflags[sourcefile] = otherflags end end end targetinfo.sourceflags = sourceflags end end -- make common items function _make_common_items(vcxprojfile, vsinfo, target) -- for each mode and arch for _, targetinfo in ipairs(target.info) do -- make common item _make_common_item(vcxprojfile, vsinfo, target, targetinfo) end end -- make header file function _make_include_file(vcxprojfile, includefile, vcxprojdir) vcxprojfile:print("", path.relative(path.absolute(includefile), vcxprojdir)) end -- make source file for all modes function _make_source_file_forall(vcxprojfile, vsinfo, target, sourcefile, sourceinfo) -- get object file and source kind local sourcekind for _, info in ipairs(sourceinfo) do sourcekind = info.sourcekind break end -- enter it local nodename if sourcekind == "as" then nodename = "CustomBuild" elseif sourcekind == "mrc" then nodename = "ResourceCompile" elseif sourcekind == "cu" then nodename = "CudaCompile" elseif sourcekind == "cc" or sourcekind == "cxx" then nodename = "ClCompile" end sourcefile = path.relative(path.absolute(sourcefile), target.project_dir) vcxprojfile:enter("<%s Include=\"%s\">", nodename, sourcefile) -- for *.asm files if sourcekind == "as" then vcxprojfile:print("false") vcxprojfile:print("Document") for _, info in ipairs(sourceinfo) do local objectfile = path.relative(path.absolute(info.objectfile), target.project_dir) local compcmd = _make_compcmd(info.compargv, sourcefile, objectfile, target.project_dir) vcxprojfile:print("%s", info.mode .. '|' .. info.arch, objectfile) vcxprojfile:print("%s", info.mode .. '|' .. info.arch, compcmd) end vcxprojfile:print("%s", path.filename(sourcefile)) -- for *.rc files elseif sourcekind == "mrc" then for _, info in ipairs(sourceinfo) do local objectfile = path.relative(path.absolute(info.objectfile), target.project_dir) vcxprojfile:print("%s", info.mode, info.arch, objectfile) end -- for *.c/cpp/cu files else -- compile as c++ modules if support.has_module_extension(sourcefile) then vcxprojfile:print("CompileAsCppModule") end -- we need to use different object directory and allow parallel building -- -- @see https://github.com/xmake-io/xmake/issues/2016 -- https://github.com/xmake-io/xmake/issues/1062 for _, info in ipairs(sourceinfo) do local objectname = path.filename(info.objectfile) local targetinfo = info.targetinfo if not targetinfo.objectnames then targetinfo.objectnames = hashset:new() end if targetinfo.objectnames:has(objectname) then local outputnode = (sourcekind == "cu" and "CompileOut" or "ObjectFileName") local objectfile = path.relative(path.absolute(info.objectfile), target.project_dir) vcxprojfile:print("<%s Condition=\"\'%$(Configuration)|%$(Platform)\'==\'%s|%s\'\">%s", outputnode, info.mode, info.arch, objectfile, outputnode) else targetinfo.objectnames:insert(objectname) end end -- init items local items = { AdditionalOptions = { key = function (info) return os.args(info.flags) end , value = function (key) return key .. " %%(AdditionalOptions)" end } } -- make items for itemname, iteminfo in pairs(items) do -- make merge keys local mergekeys = {} for _, info in ipairs(sourceinfo) do local key = iteminfo.key(info) mergekeys[key] = mergekeys[key] or {} mergekeys[key][info.mode .. '|' .. info.arch] = true end for key, mergeinfos in pairs(mergekeys) do -- merge mode and arch first local count = 0 for _, mode in ipairs(vsinfo.modes) do if mergeinfos[mode .. "|Win32"] and mergeinfos[mode .. "|x64"] then mergeinfos[mode .. "|Win32"] = nil mergeinfos[mode .. "|x64"] = nil mergeinfos[mode] = true end if mergeinfos[mode] then count = count + 1 end end -- disable the precompiled header if sourcekind ~= headerkind local pcheader = target.pcxxheader or target.pcheader local pcheader_disable = false if sourcekind == "cu" or (pcheader and language.sourcekind_of(sourcefile) ~= (target.pcxxheader and "cxx" or "cc")) then pcheader_disable = true end -- all modes and archs exist? if count == #vsinfo.modes then if #key > 0 then vcxprojfile:print("<%s>%s", itemname, iteminfo.value(key), itemname) if pcheader_disable then vcxprojfile:print("NotUsing") end end else for cond, _ in pairs(mergeinfos) do if cond:find('|', 1, true) then -- for mode | arch if #key > 0 then vcxprojfile:print("<%s Condition=\"\'%$(Configuration)|%$(Platform)\'==\'%s\'\">%s", itemname, cond, iteminfo.value(key), itemname) if pcheader_disable then vcxprojfile:print("NotUsing", cond) end end else -- only for mode if #key > 0 then vcxprojfile:print("<%s Condition=\"\'%$(Configuration)\'==\'%s\'\">%s", itemname, cond, iteminfo.value(key), itemname) if pcheader_disable then vcxprojfile:print("NotUsing", cond) end end end end end end end end -- leave it vcxprojfile:leave("", nodename) end -- make source file for specific modes function _make_source_file_forspec(vcxprojfile, vsinfo, target, sourcefile, sourceinfo) -- add source file sourcefile = path.relative(path.absolute(sourcefile), target.project_dir) for _, info in ipairs(sourceinfo) do -- enter it local nodename if info.sourcekind == "as" then nodename = "CustomBuild" elseif info.sourcekind == "mrc" then nodename = "ResourceCompile" elseif info.sourcekind == "cu" then nodename = "CudaCompile" elseif info.sourcekind == "cc" or info.sourcekind == "cxx" then nodename = "ClCompile" end vcxprojfile:enter("<%s Condition=\"\'%$(Configuration)|%$(Platform)\'==\'%s|%s\'\" Include=\"%s\">", nodename, info.mode, info.arch, sourcefile) -- for *.asm files local objectfile = path.relative(path.absolute(info.objectfile), target.project_dir) if info.sourcekind == "as" then local compcmd = _make_compcmd(info.compargv, sourcefile, objectfile, target.project_dir) vcxprojfile:print("false") vcxprojfile:print("Document") vcxprojfile:print("%s", objectfile) vcxprojfile:print("%s", compcmd) -- for *.rc files elseif info.sourcekind == "mrc" then vcxprojfile:print("%s", info.mode, info.arch, objectfile) -- for *.c/cpp/cu files else -- compile as c++ modules if support.has_module_extension(sourcefile) then vcxprojfile:print("CompileAsCppModule") end -- we need to use different object directory and allow parallel building -- -- @see https://github.com/xmake-io/xmake/issues/2016 -- https://github.com/xmake-io/xmake/issues/1062 local objectname = path.filename(objectfile) local targetinfo = info.targetinfo if not targetinfo.objectnames then targetinfo.objectnames = hashset:new() end local targetinfo = info.targetinfo local outputnode = (info.sourcekind == "cu" and "CompileOut" or "ObjectFileName") if targetinfo.objectnames:has(objectname) then vcxprojfile:print("<%s Condition=\"\'%$(Configuration)|%$(Platform)\'==\'%s|%s\'\">%s", outputnode, info.mode, info.arch, objectfile, outputnode) else targetinfo.objectnames:insert(objectname) end -- disable the precompiled header if sourcekind ~= headerkind local pcheader = target.pcxxheader or target.pcheader if pcheader and info.sourcekind ~= "cu" and language.sourcekind_of(sourcefile) ~= (target.pcxxheader and "cxx" or "cc") then vcxprojfile:print("NotUsing") end vcxprojfile:print("%s %%(AdditionalOptions)", os.args(info.flags)) end -- leave it vcxprojfile:leave("", nodename) end end -- make source file for precompiled header function _make_source_file_forpch(vcxprojfile, vsinfo, target) -- add precompiled source file local pcheader = target.pcxxheader or target.pcheader if pcheader then local sourcefile = path.relative(path.absolute(pcheader), target.project_dir) vcxprojfile:enter("", sourcefile) vcxprojfile:print("Create") vcxprojfile:print("") vcxprojfile:print(" %%(AdditionalOptions)") for _, info in ipairs(target.info) do -- compile as c/c++ local compileas = (target.pcxxheader and "CompileAsCpp" or "CompileAsC") vcxprojfile:print("%s", info.mode, info.arch, compileas) -- add object file local pcoutputfile = info.pcxxoutputfile or info.pcoutputfile if pcoutputfile then local objectfile = path.relative(path.absolute(pcoutputfile .. ".obj"), target.project_dir) vcxprojfile:print("%s", info.mode, info.arch, objectfile) end end vcxprojfile:leave("") end end -- make source files function _make_source_files(vcxprojfile, vsinfo, target) -- add source files vcxprojfile:enter("") -- make source file infos local sourceinfos = {} for _, targetinfo in ipairs(target.info) do for _, sourcebatch in pairs(targetinfo.sourcebatches) do local sourcekind = sourcebatch.sourcekind local rulename = sourcebatch.rulename if (rulename == "c.build" or rulename == "c++.build" or sourcekind == "as" or sourcekind == "mrc" or sourcekind == "cu") then local objectfiles = sourcebatch.objectfiles for idx, sourcefile in ipairs(sourcebatch.sourcefiles) do local objectfile = objectfiles[idx] local flags = targetinfo.sourceflags[sourcefile] sourceinfos[sourcefile] = sourceinfos[sourcefile] or {} table.insert(sourceinfos[sourcefile], {targetinfo = targetinfo, mode = targetinfo.mode, arch = targetinfo.arch, sourcekind = sourcekind, objectfile = objectfile, flags = flags, compargv = targetinfo.compargvs[sourcefile]}) end elseif rulename == "c++.build.modules" then local builder_batch = targetinfo.sourcebatches["c++.build.modules.builder"] table.sort(builder_batch.objectfiles) local objectfiles = builder_batch.objectfiles for idx, sourcefile in ipairs(sourcebatch.sourcefiles) do local is_named_module = table.contains(builder_batch.sourcefiles, sourcefile) if is_named_module then local objectfile = objectfiles[idx] local flags = targetinfo.sourceflags[sourcefile] sourceinfos[sourcefile] = sourceinfos[sourcefile] or {} table.insert(sourceinfos[sourcefile], {targetinfo = targetinfo, mode = targetinfo.mode, arch = targetinfo.arch, sourcekind = "cxx", objectfile = objectfile, flags = flags, compargv = targetinfo.compargvs[sourcefile]}) end end end end end -- make source files for sourcefile, sourceinfo in table.orderpairs(sourceinfos) do if #sourceinfo == #target.info then _make_source_file_forall(vcxprojfile, vsinfo, target, sourcefile, sourceinfo) else _make_source_file_forspec(vcxprojfile, vsinfo, target, sourcefile, sourceinfo) end end -- make precompiled source file _make_source_file_forpch(vcxprojfile, vsinfo, target) vcxprojfile:leave("") -- add include files local pcheader = target.pcxxheader or target.pcheader vcxprojfile:enter("") for _, includefile in ipairs(table.join(target.headerfiles or {}, target.extrafiles)) do -- we need to ignore pcheader file to fix https://github.com/xmake-io/xmake/issues/1171 if not pcheader or includefile ~= pcheader then _make_include_file(vcxprojfile, includefile, target.project_dir) end end vcxprojfile:leave("") end -- make vcxproj function make(vsinfo, target) -- the target name local targetname = target.name -- the vcxproj directory local vcxprojdir = target.project_dir -- build common flags _build_common_items(vsinfo, target) -- open vcxproj file local vcxprojpath = path.join(vcxprojdir, targetname .. ".vcxproj") local vcxprojfile = vsfile.open(vcxprojpath, "w") -- init indent character vsfile.indentchar(' ') -- make header _make_header(vcxprojfile, vsinfo) -- make Configurations _make_configurations(vcxprojfile, vsinfo, target) -- make common items _make_common_items(vcxprojfile, vsinfo, target) -- make source files _make_source_files(vcxprojfile, vsinfo, target) -- make deps references _make_references(vcxprojfile, vsinfo, target) -- make tailer _make_tailer(vcxprojfile, vsinfo, target) -- exit solution file vcxprojfile:close() end