--!A cross-platform build utility based on Lua -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. -- You may obtain a copy of the License at -- -- http://www.apache.org/licenses/LICENSE-2.0 -- -- Unless required by applicable law or agreed to in writing, software -- distributed under the License is distributed on an "AS IS" BASIS, -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -- See the License for the specific language governing permissions and -- limitations under the License. -- -- Copyright (C) 2015-present, TBOOX Open Source Group. -- -- @author ruki -- @file vs201x_vcxproj.lua -- -- imports import("core.project.config") import("core.language.language") import("vsfile") -- get toolset version function _get_toolset_ver(targetinfo, vsinfo) -- get toolset version from vs version local toolset_ver = nil local vs_toolset = config.get("vs_toolset") if vs_toolset then local verinfo = vs_toolset:split('%.') toolset_ver = "v" .. verinfo[1] .. (verinfo[2] or "0") end 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 -- 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 v = v:gsub("([%-/]I)(.*)", function (I, dir) 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 I .. dir 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 -- -Idir or /Idir flag = flag:gsub("[%-/]I(.*)", function (dir) 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 "/I" .. dir end) -- save flag 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 .. "__") -- ok? 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 = 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 "/libpath:" .. dir end) -- replace -def:dir or /def:dir flag = flag:gsub(string.ipattern("[%-/]def:(.*)"), function (dir) 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 "/def:" .. dir end) -- save flag table.insert(flags, flag) end -- ok? return flags end -- make header function _make_header(vcxprojfile, vsinfo) -- make header vcxprojfile:print("") vcxprojfile:enter("", assert(vsinfo.project_version)) end -- make tailer function _make_tailer(vcxprojfile, vsinfo) vcxprojfile:print("") vcxprojfile:enter("") vcxprojfile:leave("") vcxprojfile:leave("") end -- make Configurations function _make_configurations(vcxprojfile, vsinfo, target, vcxprojdir) -- the target name local targetname = target.name -- init configuration type local configuration_types = { binary = "Application" , shared = "DynamicLibrary" , static = "StaticLibrary" } -- 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", assert(configuration_types[target.kind])) 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("") 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("") -- make OutputDirectory and IntermediateDirectory for _, targetinfo in ipairs(target.info) do vcxprojfile:enter("", targetinfo.mode, targetinfo.arch) vcxprojfile:print("%s\\", path.relative(path.absolute(targetinfo.targetdir), vcxprojdir)) vcxprojfile:print("%s\\", path.relative(path.absolute(targetinfo.objectdir), vcxprojdir)) vcxprojfile:print("%s", path.basename(targetinfo.targetfile)) vcxprojfile:print("%s", path.extension(targetinfo.targetfile)) if target.kind == "binary" then vcxprojfile:print("true") end vcxprojfile:leave("") end end -- make source options function _make_source_options(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("[%-/]Wall") then vcxprojfile:print("EnableAllWarnings", condition) else vcxprojfile:print("TurnOffAllWarnings", condition) end if flagstr:find("[%-/]WX") then vcxprojfile:print("true", condition) end -- make PreprocessorDefinitions local defstr = "" for _, flag in ipairs(flags) do flag:gsub("[%-/]D(.*)", function (def) defstr = defstr .. 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 -- handle multi processor compilation if flagstr:find("[%-/]Gm-") or not flagstr:find("[%-/]Gm") then vcxprojfile:print("false", condition) if flagstr:find("[%-/]MP") 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, 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 AdditionalOptions local additional_flags = {} local excludes = {"Od", "Os", "O0", "O1", "O2", "Ot", "Ox", "W0", "W1", "W2", "W3", "WX", "Wall", "Zi", "ZI", "Z7", "MT", "MTd", "MD", "MDd", "TP", "Fd", "fp", "I", "D", "Gm-", "Gm"} 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(additional_flags, flag) end end if #additional_flags > 0 then vcxprojfile:print("%s %%(AdditionalOptions)", condition, os.args(additional_flags)) end end -- make common item function _make_common_item(vcxprojfile, vsinfo, target, targetinfo, vcxprojdir) -- enter ItemDefinitionGroup vcxprojfile:enter("", targetinfo.mode, targetinfo.arch) -- init the linker kinds local linkerkinds = { binary = "Link" , static = "Lib" , shared = "Link" } -- for linker? vcxprojfile:enter("<%s>", linkerkinds[targetinfo.targetkind]) -- save subsystem local subsystem = "Console" -- make linker flags local flags = {} for _, flag in ipairs(_make_linkflags(targetinfo, vcxprojdir)) do local flag_lower = flag:lower() -- remove "-subsystem:windows" if flag_lower:find("[%-/]subsystem:windows") then subsystem = "Windows" -- remove "-machine:[x86|x64]", "-pdb:*.pdb" and "-debug" elseif not flag_lower:find("[%-/]machine:%w+") and not flag_lower:find("[%-/]pdb:.+%.pdb") and not flag_lower:find("[%-/]debug") then table.insert(flags, flag) end end flags = os.args(flags) -- make AdditionalOptions vcxprojfile:print("%s %%(AdditionalOptions)", flags) -- generate debug infomation? if linkerkinds[targetinfo.targetkind] == "Link" then -- 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 compiler? vcxprojfile:enter("") -- make source options _make_source_options(vcxprojfile, targetinfo.commonflags) -- 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", path.filename(pcheader)) local pcoutputfile = targetinfo.pcxxoutputfile or targetinfo.pcoutputfile if pcoutputfile then vcxprojfile:print("%s", path.relative(path.absolute(pcoutputfile), vcxprojdir)) end vcxprojfile:print("%s;%%(ForcedIncludeFiles)", path.filename(pcheader)) end vcxprojfile:leave("") -- leave ItemDefinitionGroup vcxprojfile:leave("") end -- make common items function _make_common_items(vcxprojfile, vsinfo, target, vcxprojdir) -- for each mode and arch for _, targetinfo in ipairs(target.info) do -- make source flags local flags_stats = {} local files_count = 0 local first_flags = nil targetinfo.sourceflags = {} for _, sourcebatch in pairs(targetinfo.sourcebatches) do local sourcekind = sourcebatch.sourcekind if (sourcekind == "cc" or sourcekind == "cxx" or sourcekind == "as" or sourcekind == "mrc") then for _, sourcefile in ipairs(sourcebatch.sourcefiles) do -- make compiler flags local flags = _make_compflags(sourcefile, targetinfo, vcxprojdir) -- no common flags for asm if sourcekind ~= "as" then for _, flag in ipairs(flags) do flags_stats[flag] = (flags_stats[flag] or 0) + 1 end -- update files count files_count = files_count + 1 -- save first flags if first_flags == nil then first_flags = flags end end -- save source flags targetinfo.sourceflags[sourcefile] = flags end end end -- make common flags targetinfo.commonflags = {} for _, flag in ipairs(first_flags) do if flags_stats[flag] == files_count then table.insert(targetinfo.commonflags, flag) end end -- remove common flags from source flags local sourceflags = {} for sourcefile, flags in pairs(targetinfo.sourceflags) do local otherflags = {} for _, flag in ipairs(flags) do if flags_stats[flag] ~= files_count then table.insert(otherflags, flag) end end sourceflags[sourcefile] = otherflags end targetinfo.sourceflags = sourceflags -- make common item _make_common_item(vcxprojfile, vsinfo, target, targetinfo, vcxprojdir) end end -- make header file function _make_header_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, vcxprojdir) -- get object file and source kind local sourcekind = nil for _, info in ipairs(sourceinfo) do sourcekind = info.sourcekind break end -- enter it local nodename = (sourcekind == "as" and "CustomBuild" or (sourcekind == "mrc" and "ResourceCompile" or "ClCompile")) sourcefile = path.relative(path.absolute(sourcefile), vcxprojdir) 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), vcxprojdir) local compcmd = _make_compcmd(info.compargv, sourcefile, objectfile, vcxprojdir) 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), vcxprojdir) vcxprojfile:print("%s", info.mode, info.arch, objectfile) end -- for *.c/cpp files else -- 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 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, vcxprojdir) -- add source file sourcefile = path.relative(path.absolute(sourcefile), vcxprojdir) for _, info in ipairs(sourceinfo) do -- enter it local nodename = (info.sourcekind == "as" and "CustomBuild" or (info.sourcekind == "mrc" and "ResourceCompile" or "ClCompile")) 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), vcxprojdir) if info.sourcekind == "as" then local compcmd = _make_compcmd(info.compargv, sourcefile, objectfile, vcxprojdir) vcxprojfile:print("false") vcxprojfile:print("Document") vcxprojfile:print("%s", objectfile) vcxprojfile:print("%s", compcmd) -- for *.rc files elseif sourcekind == "mrc" then vcxprojfile:print("%s",info.mode,info.arch,objectfile) -- for *.c/cpp files else -- disable the precompiled header if sourcekind ~= headerkind local pcheader = target.pcxxheader or target.pcheader if pcheader 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, vcxprojdir) -- add precompiled source file local pcheader = target.pcxxheader or target.pcheader if pcheader then local sourcefile = path.relative(path.absolute(pcheader), vcxprojdir) 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"), vcxprojdir) vcxprojfile:print("%s", info.mode, info.arch, objectfile) end end vcxprojfile:leave("") end end -- make source files function _make_source_files(vcxprojfile, vsinfo, target, vcxprojdir) -- 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 if (sourcekind == "cc" or sourcekind == "cxx" or sourcekind == "as" or sourcekind == "mrc") 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], {mode = targetinfo.mode, arch = targetinfo.arch, sourcekind = sourcekind, objectfile = objectfile, flags = flags, compargv = targetinfo.compargvs[sourcefile]}) end end end end -- make source files for sourcefile, sourceinfo in pairs(sourceinfos) do if #sourceinfo == #target.info then _make_source_file_forall(vcxprojfile, vsinfo, target, sourcefile, sourceinfo, vcxprojdir) else _make_source_file_forspec(vcxprojfile, vsinfo, target, sourcefile, sourceinfo, vcxprojdir) end end -- make precompiled source file _make_source_file_forpch(vcxprojfile, vsinfo, target, vcxprojdir) vcxprojfile:leave("") -- add header files local pcheader = target.pcxxheader or target.pcheader vcxprojfile:enter("") for _, includefile in ipairs(target.headerfiles) do -- we need ignore pcheader file to fix https://github.com/xmake-io/xmake/issues/1171 if not pcheader or includefile ~= pcheader then _make_header_file(vcxprojfile, includefile, vcxprojdir) end end vcxprojfile:leave("") end -- make vcxproj function make(vsinfo, target) -- the target name local targetname = target.name -- the vcxproj directory local vcxprojdir = path.join(vsinfo.solution_dir, targetname) -- 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, vcxprojdir) -- make common items _make_common_items(vcxprojfile, vsinfo, target, vcxprojdir) -- make source files _make_source_files(vcxprojfile, vsinfo, target, vcxprojdir) -- make tailer _make_tailer(vcxprojfile, vsinfo) -- exit solution file vcxprojfile:close() end