--!The Make-like Build Utility based on Lua -- -- Licensed to the Apache Software Foundation (ASF) under one -- or more contributor license agreements. See the NOTICE file -- distributed with this work for additional information -- regarding copyright ownership. The ASF licenses this file -- to you 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 - 2017, TBOOX Open Source Group. -- -- @author ruki -- @file vs201x_vcxproj.lua -- -- imports import("core.project.config") import("vsfile") -- make compiling flags function _make_compflags(sourcefile, targetinfo, vcxprojdir) -- translate path for -Idir or /Idir, -Fdsymbol.pdb or /Fdsymbol.pdb local flags = {} for _, flag in ipairs(targetinfo.compflags[sourcefile]) do -- -Idir or /Idir flag = flag:gsub("[%-|/]I(.*)", function (dir) dir = dir:trim() if not path.is_absolute(dir) then dir = path.relative(path.absolute(dir), vcxprojdir) end return "/I" .. dir end) -- -Fdsymbol.pdb or /Fdsymbol.pdb flag = flag:gsub("[%-|/]Fd(.*)", function (dir) dir = dir:trim() if not path.is_absolute(dir) then dir = path.relative(path.absolute(dir), vcxprojdir) end return "/Fd" .. dir end) -- save flag table.insert(flags, flag) end -- ok? return flags end -- make linking flags function _make_linkflags(targetinfo, vcxprojdir) -- replace -libpath:dir or /libpath:dir, -pdb:symbol.pdb or /pdb:symbol.pdb local flags = {} for _, flag in ipairs(targetinfo.linkflags) do -- replace -libpath:dir or /libpath:dir flag = flag:gsub("[%-|/]libpath:(.*)", function (dir) dir = dir:trim() if not path.is_absolute(dir) then dir = path.relative(path.absolute(dir), vcxprojdir) end return "/libpath:" .. dir end) -- replace -pdb:symbol.pdb or /pdb:symbol.pdb flag = flag:gsub("[%-|/]pdb:(.*)", function (dir) dir = dir:trim() if not path.is_absolute(dir) then dir = path.relative(path.absolute(dir), vcxprojdir) end return "/pdb:" .. dir end) -- save flag table.insert(flags, flag) end -- ok? return flags end -- make header function _make_header(vcxprojfile, vsinfo) -- the versions local versions = { vs2010 = '4' , vs2012 = '4' , vs2013 = '12' , vs2015 = '14' , vs2017 = '15' } -- make header vcxprojfile:print("") vcxprojfile:enter("", assert(versions["vs" .. vsinfo.vstudio_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" } -- the toolset versions local toolset_versions = { vs2010 = "100" , vs2012 = "110" , vs2013 = "120" , vs2015 = "140" , vs2017 = "141" } -- the sdk version local sdk_versions = { vs2015 = "10.0.10240.0" , vs2017 = "10.0.14393.0" } -- 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}", os.uuid(targetname)) vcxprojfile:print("%s", targetname) if vsinfo.vstudio_version >= "2015" then vcxprojfile:print("%s", sdk_versions["vs" .. vsinfo.vstudio_version]) 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("v%s", assert(toolset_versions["vs" .. vsinfo.vstudio_version])) vcxprojfile:print("MultiByte") 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(config.get("buildir")), vcxprojdir)) vcxprojfile:print("%$(Configuration)\\") 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 = table.concat(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 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 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) elseif flagstr:find("[%-|/]MT") then vcxprojfile:print("MultiThreaded", condition) end -- complie as c++ if exists flag: /TP if flagstr:find("[%-|/]TP") then vcxprojfile:print("CompileAsCpp", condition) end -- make AdditionalOptions local additional_flags = {} local excludes = {"Os", "O0", "O1", "O2", "Ot", "Ox", "W0", "W1", "W2", "W3", "WX", "Wall", "Zi", "ZI", "Z7", "MT", "MTd", "MD", "MDd", "TP"} 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, table.concat(additional_flags, " "):trim()) end end -- make common item function _make_common_item(vcxprojfile, vsinfo, targetinfo, vcxprojdir) -- enter ItemDefinitionGroup vcxprojfile:enter("", targetinfo.mode, targetinfo.arch) -- for linker? if targetinfo.targetkind == "binary" then vcxprojfile:enter("") -- make linker flags local flags = table.concat(_make_linkflags(targetinfo, vcxprojdir), " "):trim() -- make AdditionalOptions vcxprojfile:print("%s %%(AdditionalOptions)", flags) -- generate 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)) -- make SubSystem vcxprojfile:print("Console") -- make TargetMachine vcxprojfile:print("%s", ifelse(targetinfo.arch == "x64", "MachineX64", "MachineX86")) vcxprojfile:leave("") end -- for compiler? vcxprojfile:enter("") -- make source options _make_source_options(vcxprojfile, targetinfo.commonflags) -- make ProgramDataBaseFileName (default: empty) vcxprojfile:print("") 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 sourcekind, sourcebatch in pairs(targetinfo.sourcebatches) do if sourcekind == "cc" or sourcekind == "cxx" then for _, sourcefile in ipairs(sourcebatch.sourcefiles) do -- make compiler flags local flags = _make_compflags(sourcefile, targetinfo, vcxprojdir) 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 -- 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, 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, sourcefile, sourceinfo, vcxprojdir) -- get object file local objectfile = nil for _, info in ipairs(sourceinfo) do objectfile = info.objectfile break end -- enter it vcxprojfile:enter("", path.relative(path.absolute(sourcefile), vcxprojdir)) -- make ObjectFileName vcxprojfile:print("%s", path.relative(path.absolute(objectfile), vcxprojdir)) -- make AdditionalOptions local mergeflags = {} for _, info in ipairs(sourceinfo) do local flags = table.concat(info.flags, " "):trim() if flags ~= "" then mergeflags[flags] = mergeflags[flags] or {} mergeflags[flags][info.mode .. '|' .. info.arch] = true end end for flags, mergeinfos in pairs(mergeflags) 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 -- all modes and archs exist? if count == #vsinfo.modes then vcxprojfile:print("%s %%(AdditionalOptions)", flags) else for cond, _ in pairs(mergeinfos) do if cond:find('|', 1, true) then -- for mode | arch vcxprojfile:print("%s %%(AdditionalOptions)", cond, flags) else -- only for mode vcxprojfile:print("%s %%(AdditionalOptions)", cond, flags) end end end end -- leave it vcxprojfile:leave("") end -- make source file for specific modes function _make_source_file_forspec(vcxprojfile, vsinfo, sourcefile, sourceinfo, vcxprojdir) -- add source file for _, info in ipairs(sourceinfo) do -- enter it vcxprojfile:enter("", info.mode, info.arch, path.relative(path.absolute(sourcefile), vcxprojdir)) -- make ObjectFileName vcxprojfile:print("%s", path.relative(path.absolute(info.objectfile), vcxprojdir)) -- get source flags local flags = table.concat(info.flags, " "):trim() -- make AdditionalOptions if flags ~= "" then vcxprojfile:print("%s %%(AdditionalOptions)", flags) end -- leave it vcxprojfile:leave("") end end -- make source files function _make_source_files(vcxprojfile, vsinfo, target, vcxprojdir) -- enter ItemGroup vcxprojfile:enter("") -- make source file infos local sourceinfos = {} for _, targetinfo in ipairs(target.info) do for sourcekind, sourcebatch in pairs(targetinfo.sourcebatches) do if sourcekind == "cc" or sourcekind == "cxx" 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, objectfile = objectfile, flags = flags}) 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, sourcefile, sourceinfo, vcxprojdir) else _make_source_file_forspec(vcxprojfile, vsinfo, sourcefile, sourceinfo, vcxprojdir) end end vcxprojfile:leave("") -- enter header group vcxprojfile:enter("") -- add headers for _, includefile in ipairs(target.headerfiles) do _make_header_file(vcxprojfile, includefile, vcxprojdir) 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 vcxprojfile = vsfile.open(path.join(vcxprojdir, targetname .. ".vcxproj"), "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