--!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