--!A cross-platform 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 - 2018, TBOOX Open Source Group.
--
-- @author ruki
-- @file vs201x_vcxproj.lua
--
-- imports
import("core.project.config")
import("core.language.language")
import("vsfile")
-- 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 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("[%-|/]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)
-- 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 default sdk version
local sdk_versions =
{
vs2015 = "10.0.10240.0"
, vs2017 = "10.0.14393.0"
}
-- get sdk version
local sdkver = nil
for _, targetinfo in ipairs(target.info) do
sdkver = targetinfo.sdkver
if sdkver then
break
end
end
-- 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.uuid(targetname))
vcxprojfile:print("%s", targetname)
if vsinfo.vstudio_version >= "2015" then
vcxprojfile:print("%s", sdkver or 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)\\")
vcxprojfile:print("%s", path.basename(targetinfo.targetfile))
vcxprojfile:print("%s", path.extension(targetinfo.targetfile))
vcxprojfile:print("%s", path.relative(path.absolute(targetinfo.targetfile), vcxprojdir))
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 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
-- 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
-- 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", "Fd", "fp", "I"}
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])
-- make linker flags
local flags = {}
for _, flag in ipairs(_make_linkflags(targetinfo, vcxprojdir)) do
-- remove "-machine:[x86|x64]", "-pdb:*.pdb" and "-debug"
if not flag:find("[%-/]machine:%w+") and not flag:find("[%-/]pdb:.+%.pdb") and not flag: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))
-- make *.pdb file path
local symbolfile = targetinfo.symbolfile
if symbolfile then
vcxprojfile:print("%s", path.relative(path.absolute(symbolfile), vcxprojdir))
end
end
-- make SubSystem
if targetinfo.targetkind == "binary" then
vcxprojfile:print("Console")
end
-- make TargetMachine
vcxprojfile:print("%s", ifelse(targetinfo.arch == "x64", "MachineX64", "MachineX86"))
-- make OutputFile
vcxprojfile:print("%s", path.relative(path.absolute(targetinfo.targetfile), vcxprojdir))
vcxprojfile:leave("%s>", linkerkinds[targetinfo.targetkind])
-- for compiler?
vcxprojfile:enter("")
-- make source options
_make_source_options(vcxprojfile, targetinfo.commonflags)
-- make *.pdb file path
local symbolfile = targetinfo.symbolfile
if symbolfile then
vcxprojfile:print("%s", path.relative(path.absolute(symbolfile), vcxprojdir))
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", path.filename(pcheader))
local pcoutputfile = targetinfo.pcxxoutputfile or targetinfo.pcoutputfile
if pcoutputfile then
vcxprojfile:print("%s", path.relative(path.absolute(pcoutputfile), vcxprojdir))
end
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 sourcekind, sourcebatch in pairs(targetinfo.sourcebatches) do
if not sourcebatch.rulename and (sourcekind == "cc" or sourcekind == "cxx" or sourcekind == "as") 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
sourcefile = path.relative(path.absolute(sourcefile), vcxprojdir)
vcxprojfile:enter("<%s Include=\"%s\">", ifelse(sourcekind == "as", "CustomBuild", "ClCompile"), 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)
vcxprojfile:print("%s", info.mode .. '|' .. info.arch, objectfile)
vcxprojfile:print("%s /nologo /c %s -Fo%s %s", info.mode .. '|' .. info.arch, ifelse(info.arch == "x64", "ml64", "ml"), os.args(info.flags), objectfile, sourcefile)
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
}
, ObjectFileName =
{
key = function (info) return path.relative(path.absolute(info.objectfile), vcxprojdir) end
, value = function (key) return key 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) ~= ifelse(target.pcxxheader, "cxx", "cc") then
pcheader_disable = true
end
-- all modes and archs exist?
if count == #vsinfo.modes then
if #key > 0 then
vcxprojfile:print("<%s>%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%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%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("%s>", ifelse(sourcekind == "as", "CustomBuild", "ClCompile"))
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
vcxprojfile:enter("<%s Condition=\"\'%$(Configuration)|%$(Platform)\'==\'%s|%s\'\" Include=\"%s\">", ifelse(info.sourcekind == "as", "CustomBuild", "ClCompile"), info.mode, info.arch, sourcefile)
-- for *.asm files
local objectfile = path.relative(path.absolute(info.objectfile), vcxprojdir)
if info.sourcekind == "as" then
vcxprojfile:print("false")
vcxprojfile:print("Document")
vcxprojfile:print("%s", objectfile)
vcxprojfile:print("%s /nologo /c %s -Fo%s %s", ifelse(info.arch == "x64", "ml64", "ml"), os.args(info.flags), objectfile, sourcefile)
-- 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) ~= ifelse(target.pcxxheader, "cxx", "cc") then
vcxprojfile:print("NotUsing")
end
vcxprojfile:print("%s", objectfile)
vcxprojfile:print("%s %%(AdditionalOptions)", os.args(info.flags))
end
-- leave it
vcxprojfile:leave("%s>", ifelse(info.sourcekind == "as", "CustomBuild", "ClCompile"))
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 = ifelse(target.pcxxheader, "CompileAsCpp", "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 sourcekind, sourcebatch in pairs(targetinfo.sourcebatches) do
if not sourcebatch.rulename and (sourcekind == "cc" or sourcekind == "cxx" or sourcekind == "as") 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})
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 headers
vcxprojfile:enter("")
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