1.问题或需求描述:

手动创建VS2019 C++工程在某些场景下可能过于繁琐且易出错,例如引用类库可能需要同时配置多个编译模式(debug,realse);可能需要每次手动导入一些基础代码;或许使用一个标准工程副本可以解决问题,但还不够灵活。

2.测试环境:

win10 x64, VS2019 v142

3.解决方法或原理:

3.1方法:

1>将相关库、基础模板等集中打包在一个压缩文件(例:stdlib_tmpl.zip)中。

2>根据使用场景配置相关lua配置文件,然后从lua配置文件快速创建msvc c++工程(自动从模板代码压缩文件'stdlib_tmpl.zip'中抽取所需代码)

3.2方案:

下载链接:https://[百度网盘]/s/1V_5ng4g64OwA5yVX7c_LfA?pwd=cpp0  提取码:cpp0

从配置文件自动创建MSVC C++工程_c++

gen_vcx_proj options:
  -h [ --help ]                       produce help message
  -f [ --config ] arg (=./config.lua) builder config lua
  -m [ --make ]                       make builder config lua template
  -c [ --console ]                    console builder
  -d [ --dll ]                        dll builder

从配置文件自动创建MSVC C++工程_dll_02

1>gen_vcx_proj.exe 用法示例:

生成console工程lua配置模板:gen_vcj_proj -mc -f console-cfg.lua

生成dll工程lua配置模板:gen_vcj_proj -md -f dll-cfg.lua

构建console工程:gen_vcj_proj -c -f console-cfg.lua

构建dll工程:gen_vcj_proj -d -f dll-cfg.lua

2>示例 console-cfg.lua:

pro_name = "MyApplication"
root_namespace = "MyApplication"
platform_ver="v142"
project_ver="16.0"
sdk_ver="10.0"
configuration_type = "Application"
charset="Unicode"
head_files = {"framework.h", "utils.h", "udf.h"}
source_files = {"main.cpp", "utils.cpp"}

zip.extract_dir("./stdlib_tmpl.zip", "libs", "./" .. pro_name .. "/libs")
zip.extract_file("./stdlib_tmpl.zip", "console/framework.h", "./" .. pro_name .. "/framework.h")
zip.extract_file("./stdlib_tmpl.zip", "console/udf.h", "./" .. pro_name .. "/udf.h")
zip.extract_file("./stdlib_tmpl.zip", "console/utils.h", "./" .. pro_name .. "/utils.h")
zip.extract_file("./stdlib_tmpl.zip", "console/utils.cpp", "./" .. pro_name .. "/utils.cpp")
zip.extract_file("./stdlib_tmpl.zip", "console/main/cmd_program_option.cpp", "./" .. pro_name .. "/main.cpp")
zip.extract_file("./stdlib_tmpl.zip", ".gitattributes", "./" .. pro_name .. "/.gitattributes")
zip.extract_file("./stdlib_tmpl.zip", ".gitignore", "./" .. pro_name .. "/.gitignore")

function contains(array, element)
    for _, value in ipairs(array) do
        if value == element then
            return true
        end
    end
    return false
end

function array_union(array1, array2)
    local result = {}

    for _, v in ipairs(array1) do
        if not contains(result, v) then
            table.insert(result, v)
        end
    end

    for _, v in ipairs(array2) do
        if not contains(result, v) then
            table.insert(result, v)
        end
    end

    return result
end

function gen_exports_macro(input_str)
    local result = ""
    
    for i = 1, #input_str do
        local char = input_str:sub(i, i)
        if char:match("[%a%d]") then
            result = result .. char:upper()
        end
    end
    
    return result
end

local LibraryConfig = {
    is_use = false,
    compile = '',
    inner_dir = '',
    outer_dir = '',
    inc_dirs = {},
    inc_cpps = {},
    lib_dirs = {},
    depends = {},
    pre_def = {}
}

function LibraryConfig:new(is_use, compile, inner_dir, outer_dir, inc_dirs, inc_cpps, lib_dirs, depends, pre_def)
    local o = {}
    setmetatable(o, self)
    self.__index = self
    o.is_use = is_use or self.is_use
    o.compile = compile or self.compile
    o.inner_dir = inner_dir or self.inner_dir
    o.outer_dir = outer_dir or self.outer_dir
    o.inc_dirs = inc_dirs or self.inc_dirs
    o.inc_cpps = inc_cpps or self.inc_cpps
    o.lib_dirs = lib_dirs or self.lib_dirs
    o.depends = depends or self.depends
    o.pre_def = pre_def or self.pre_def
    return o
end

function LibraryConfig:to_string()
    local str = ''
    str = str .. 'is_use: ' .. tostring(self.is_use) .. '\n'
    str = str .. 'compile: ' .. tostring(self.compile) .. '\n'
    str = str .. 'inner_dir: ' .. tostring(self.inner_dir) .. '\n'
    str = str .. 'outer_dir: ' .. tostring(self.outer_dir) .. '\n'
    str = str .. 'inc_dirs: ' .. table.concat(self.inc_dirs, '') .. '\n'
    str = str .. 'inc_cpps: ' .. table.concat(self.inc_cpps, '') .. '\n'
    str = str .. 'lib_dirs: ' .. table.concat(self.lib_dirs, '') .. '\n'
    str = str .. 'depends: ' .. table.concat(self.depends, ';') .. '\n'
    str = str .. 'pre_def: ' .. table.concat(self.pre_def, ';')
    return str
end

boost_cfg = LibraryConfig:new(
    true, '', nil, nil,
    {'$(BOOST_ROOT)'},
    {},
    {'$(BOOST_LIB)'},
    {},
    {'USE_BOOST'}
    )

easylogging_cfg = LibraryConfig:new(
    true, '', nil, nil,
    {'./libs/easylogging++'},
    {'./libs/easylogging++/easylogging++.cc'},
    {},
    {},
    {'ELPP_THREAD_SAFE', 'USE_EASYLOGGINGPP'}
    )

loguru_cfg = LibraryConfig:new(
    true, '', nil, nil,
    {'./libs/loguru'},
    {'./libs/loguru/loguru.cpp'},
    {},
    {},
    {'USE_LOGURU'}
    )

getopt_cfg = LibraryConfig:new(
    false, '', nil, nil,
    {'./libs/getopt'},
    {},
    {},
    {},
    {'USE_GETOPT'}
    )

opencv2_d_cfg = LibraryConfig:new(
    true, 'debug', nil, nil,
    {'$(opencv2_include)'},
    {},
    {'$(opencv2_lib)'},
    {'$(opencv2_lib_world_d)'},
    {'USE_OPENCV2'}
    )

opencv2_r_cfg = LibraryConfig:new(
    true, 'release', nil, nil,
    {'$(opencv2_include)'},
    {},
    {'$(opencv2_lib)'},
    {'$(opencv2_lib_world)'},
    {'USE_OPENCV2'}
    )

halcon_cfg = LibraryConfig:new(
    true, '', nil, nil,
    {'$(HALCONROOT)/include', '$(HALCONROOT)/include/halconcpp'},
    {},
    {'$(HALCONROOT)/lib/$(HALCONARCH)'},
    {'halconcpp.lib'},
    {'USE_HALCON'}
    )

json_cfg = LibraryConfig:new(
    true, '', nil, nil,
    {'./libs/json-3.11.2'},
    {},
    {},
    {},
    {'USE_JSON'}
    )

sqlite_cfg = LibraryConfig:new(
    false, '', nil, nil,
    {'./libs/sqlite-3420000'},
    {},
    {},
    {},
    {'USE_SQLITE'}
    )

libiconv_cfg = LibraryConfig:new(
    true, '', nil, nil,
    {'./libs/libiconv-1.17/include'},
    {},
    {'./libs/libiconv-1.17/lib'},
    {'libiconv_a.lib'},
    {'USE_LIBICONV'}
    )

lua_cfg = LibraryConfig:new(
    true, '', nil, nil,
    {'./libs/lua-5.4.6/include'},
    {},
    {'./libs/lua-5.4.6/x64/vc16/lib'},
    {'lua5.4.6.lib'},
    {'USE_LUA'}
    )

thread_pool_cfg = LibraryConfig:new(
    false, '', nil, nil,
    {'./libs/ThreadPool'},
    {},
    {},
    {},
    {'USE_THREADPOOL'}
    )

tinyxml2_cfg = LibraryConfig:new(
    false, '', nil, nil,
    {'./libs/tinyxml2'},
    {'./libs/tinyxml2/tinyxml2.cpp'},
    {},
    {},
    {'USE_TINYXML2'}
    )

libs = {}
table.insert (libs, boost_cfg)
table.insert (libs, easylogging_cfg)
table.insert (libs, loguru_cfg)
table.insert (libs, getopt_cfg)
table.insert (libs, opencv2_d_cfg)
table.insert (libs, opencv2_r_cfg)
table.insert (libs, halcon_cfg)
table.insert (libs, json_cfg)
table.insert (libs, sqlite_cfg)
table.insert (libs, libiconv_cfg)
table.insert (libs, lua_cfg)
table.insert (libs, thread_pool_cfg)
table.insert (libs, tinyxml2)

-- debug
--~ for i, v in ipairs(libs) do
--~     print(i,':')
--~     print(v:to_string())
--~ end

for _, v in ipairs(libs) do
    if v.is_use then
        source_files = array_union(source_files, v.inc_cpps)
    end
end

local Itemdefinition = {
    compile = "Debug|x64",
    inc_dirs = {},
    lib_dirs = {},
    depends = {"%(AdditionalDependencies)"},
    pre_def = {"WIN32", "_DEBUG", "_CONSOLE", "ELPP_THREAD_SAFE", "%(PreprocessorDefinitions)"},
    sub_system = "Console",
    warning_level = "Level3"
}

function Itemdefinition:new(compile, inc_dirs, lib_dirs, depends, pre_def, sub_system, warning_level)
    local o = {}
    setmetatable(o, self)
    self.__index = self
    o.compile = compile or self.compile
    o.inc_dirs = inc_dirs or self.inc_dirs
    o.lib_dirs = lib_dirs or self.lib_dirs
    o.depends = depends or self.depends
    o.pre_def = pre_def or self.pre_def
    o.sub_system = sub_system or self.sub_system
    o.warning_level = warning_level or self.warning_level
    return o
end

Debug_Win32 = Itemdefinition:new("Debug|Win32",
    {},
    {},
    {"%(AdditionalDependencies)"},
    {"WIN32", "_DEBUG", "_CONSOLE", "%(PreprocessorDefinitions)"}
    )
Release_Win32 = Itemdefinition:new("Release|Win32",
    {},
    {},
    {"%(AdditionalDependencies)"},
    {"WIN32", "NDEBUG", "_CONSOLE", "%(PreprocessorDefinitions)"}
    )
Debug_x64 = Itemdefinition:new("Debug|x64",
    {},
    {},
    {"%(AdditionalDependencies)"},
    {"_DEBUG", "_CONSOLE", "%(PreprocessorDefinitions)"}
    )
Release_x64 = Itemdefinition:new("Release|x64",
    {},
    {},
    {"%(AdditionalDependencies)"},
    {"NDEBUG", "_CONSOLE", "%(PreprocessorDefinitions)"}
    )

compile_configs = { Debug_Win32, Release_Win32, Debug_x64, Release_x64}
for i, v in ipairs(compile_configs) do
    inc_dirs = {}
    lib_dirs = {}
    depends = {}
    pre_def = {}
    for i2, v2 in ipairs(libs) do
        if v2.is_use then
            if v2.compile ~= nil and string.len(v2.compile) > 0 then
                if string.find(string.lower(v.compile), string.lower(v2.compile)) ~= nil then
                    inc_dirs = array_union(inc_dirs, v2.inc_dirs)
                    lib_dirs = array_union(lib_dirs, v2.lib_dirs)
                    depends  = array_union(depends, v2.depends)
                    pre_def  = array_union(pre_def, v2.pre_def)
                end
            elseif v2.compile == nil or string.len(v2.compile) == 0 then
                inc_dirs = array_union(inc_dirs, v2.inc_dirs)
                lib_dirs = array_union(lib_dirs, v2.lib_dirs)
                depends  = array_union(depends, v2.depends)
                pre_def  = array_union(pre_def, v2.pre_def)
            end
        end
    end
    v.inc_dirs = array_union(inc_dirs, v.inc_dirs)
    v.lib_dirs = array_union(lib_dirs, v.lib_dirs)
    v.depends  = array_union(depends, v.depends)
    v.pre_def  = array_union(pre_def, v.pre_def)
end

--debug
--~ for _, config in ipairs(compile_configs) do
--~     print("compile: " .. config.compile)
--~     print("inc_dirs: " .. table.concat(config.inc_dirs, ";"))
--~     print("lib_dirs: " .. table.concat(config.lib_dirs, ";"))
--~     print("depends: " .. table.concat(config.depends, ";"))
--~     print("pre_def: " .. table.concat(config.pre_def, ";"))
--~     print("sub_system: " .. config.sub_system)
--~     print("warning_level: " .. config.warning_level)
--~     print("------")
--~ end

3>关于lua配置文件:

3.1>lua配置文件需要配置的字段:

pro_name:字符串
root_namespace:字符串
platform_ver:字符串
project_ver:字符串
sdk_ver:字符串
configuration_type:字符串
charset:字符串
head_files:字符串数组
source_files:字符串数组 
compile_configs:自定义结构数组(参考示例)

3.2>lua zip api:

--从zip中抽取目录(zip_file_path, inner_dir_path, outer_dir_path)
zip.extract_dir("./stdlib_tmpl.zip", "libs", "./" .. pro_name .. "/libs")
--从zip中抽取文件(zip_file_path, inner_file_path, outer_file_path)
zip.extract_file("./stdlib_tmpl.zip", "console/framework.h", "./" .. pro_name .. "/framework.h")

3.3> LibraryConfig 结构

local LibraryConfig = {
    is_use = false, --是否启用库
    compile = '',   --编译模式,debug,release,nil(nil或空字符串匹配所有模式)
    inner_dir = '', --库位于zip中的路径(需要在脚本中自行实现)
    outer_dir = '', --需要抽取到的外部目标路径(需要在脚本中自行实现)
    inc_dirs = {}, --附加头文件包含目录
    inc_cpps = {}, --包含源代码(*.cpp)(适用某些库需包含源代码)
    lib_dirs = {}, --库文件目录
    depends = {}, --库文件
    pre_def = {} --定义预处理符号
}

3.4> Itemdefinition 结构

local Itemdefinition = {
    compile = "Debug|x64",
    inc_dirs = {},
    lib_dirs = {},
    depends = {"%(AdditionalDependencies)"},
    pre_def = {"WIN32", "_DEBUG", "_CONSOLE", "ELPP_THREAD_SAFE", "%(PreprocessorDefinitions)"},
    sub_system = "Console",
    warning_level = "Level3"
}

4.结果演示

从配置文件自动创建MSVC C++工程_console_03

从配置文件自动创建MSVC C++工程_lua_04