文章目录
VS2019 - 修正导出项目的工程模板的错误
概述
再做实验,写了一些小工程,来验证知识点。
这些小工程是连续的,都是上一个知识点的基础上,进行迭代。
如果开始进一步的新实验,比较简单的方法,是将上一个实验的工程目录拷贝改名为新实验工程。
但是这样的不好的地方是,自己要改.sln, .vcxproj, vcxproj.filters的名称,改的挺麻烦的。
如果只在工程中,改工作区的解决方案和工程的名称,做实验不影响,但是磁盘上的几个工程文件名字没改过来,看着挺膈应。
尝试用VS2019导出工程模板的功能,然后新建自己导出的工程模板,这样挺好的。
但是发现VS2019(社区版)导出模板的功能有bug, 导致从自己导出的模板上新建工程时,工程文件位置不对,工程文件丢失,导致工程无法运行。
举个例子
原始工程布局如下:
D:.
| awesomeface.png
| container.jpg
| fs.cfg
| glad.c
| main.cpp
| my_gl_exp5d1a.sln
| my_gl_exp5d1a.vcxproj
| my_gl_exp5d1a.vcxproj.filters
| my_gl_exp5d1a.vcxproj.user
| vs.cfg
| wall.jpg
|
\---bin_x64_Debug
glfw3.dll
将此工程用VS2019的导出模板功能导出后,可以编译过
但是丢了2给原来包含在工程中的配置文件,导致不能运行
从模板上新建的工程,文件布局如下
| awesomeface.png
| container.jpg
| glad.c
| main.cpp
| my_gl_exp5d1a4.sln
| my_gl_exp5d1a4.vcxproj
| my_gl_exp5d1a4.vcxproj.filters
| my_gl_exp5d1a4.vcxproj.user
| wall.jpg
|
\---bin_x64_Debug
glfw3.dll
可以看到2个配置文件*.cfg丢了。
手工打开模板压缩包看看
尝试解压,发现zip包头错误,但是文件都能解开。
直觉: VS2019做的zip包是变形的,他们自己可能写入了非标的头标记,用来判断是否为自己的工程模板的格式。
可以VS2019导出的zip包,发现除了*.vstemplate之外,其余文件都和原始的工程是一样的。
直觉: …vstemplate是个配置文件,写着新建工程要落地的文件的列表。
打开…vstemplate看看,果然如此。
看看丢的那2个.cfg在…vstemplate中么?
在,但是位置写错了,莫名奇妙的写错文件路径了。
手工改过来,重新打包,VS2019不认了。无法从改过的工程模板配置新建工程。
最后才搞清楚,原来是工程模板.zip下面,需要直接有.vstemplate才行。相当于工程模板.zip包对应的就是工程的根目录才行。
补齐丢失的2个cfg, 将.vstemplate中的cfg内容和路径都改对,重新打包。
压缩时,名字随便起名,不影响。vs2019是在新建工程向导时,自动解开zip去找…vstemplate, 填在工程向导的条目中。
自己重新打包的VS2019工程模板.zip, 打开后,必须根目录下就有.vstemplate才有效。
在新建工程时,在搜索框中输入自己模板的描述的字符串(e.g. vs2019), 就能找到自己的工程模板,然后新建工程就行。
从自己修正过的模板中新建工程后,编译运行都正常。
但是,如果文件布局稍微复杂一点的工程,不知道VS2019导出模板时,会搞出什么离谱的操作。
如果工程中有几百个文件,手工来修改.vstemplate, 有点不现实,且身为工程师,也不应该手工去改,而是将.vstemplate的格式搞清楚后,写个程序来生成.vstemplate才合理。
想查一下,为啥用7zip解开VS2019导出的原始模板工程.zip会出现头错误
开始直觉是VS2019采用了zip流程,但是改了特定的zip头标记,作为自己判断zip包是否为VS2019工程模板.zip。
当时没多想,去下载了7zip的源码包,在本地编译过(编译出了一个命令行版本的7z.exe).
编译7zip源码为7z.exe的实验,写了笔记(VS2019 - 7zip工程的编译).
VS2019调试命令行为t vs2019_prj_template_1.zip
单步了一下,挺复杂的。
最后跟到报头错误的地方,看到了比较是2个文件名不一样,本来应该是一样的才对。
一个文件是包头文件索引中的文件名,一个是改文件名对应的压缩数据。
这明显就是VS2019的bug了。
单步7z.exe,只能证明是VS2019有bug. 对解决问题(VS2019导出当前工程,作为工程模板)并没有帮助。
但是,让我定位到了,确实"VS2019导出工程模板"的功能有bug, 不是我瞎猜的。仅此而已。
想自己写个工具来生成VS2019可用的工程模板
前面已经想过了,如果纯手工来修复VS2019的工程模板.zip, 如果工程中有100+的文件,受不了。
即使工程中就有3,5个文件,如果需要经常性的做实验工程的模板,也受不了。
既然如此,自己手搓一个工具,将自己想要的工程生成VS2019的工程模板,这样才是正道。
但是,如果按照商业化程序的做法来写这个工具,明显付出和收获不成正比。
在平衡了付出和收获的平衡点之后,采用的方案如下:
- 将选定工程的垃圾都去掉
- 手搓命令行工具,对选定的目录生成VS2019适配的.vstemplate, 且这个.vstemplate就在选定工程的根目录生成。
- 将工程根目录的全部东西都选中(包括.vstemplate),右击,用7zip(或者其他zip工具)打包成.zip(名称随便,只要不和现有的工程模板.zip重名就行,且文件后缀是.zip)
- 将自己做的工程模板.zip丢到VS2019的工程模板目录下
- 新建工程时,输入自己工程模板描述中的关键字(e.g. 2019), 就能找到自己的工程模板,然后就可以从自己的工程模板新建自己的工程了。
- 这种方法,特别适合于做一个连续迭代的实验.
笔记
花了2天,手搓了一个工具.
编程环境 : vs2019 c++ console
效果如下:
D:\my_dev\my_tools\vs2019_prj_template\case>genVs2019PrjTemplateFile.exe D:\my_dev\my_tools\vs2019_prj_template\case\my_gl_exp5d1a
genVs2019PrjTemplateFile 0.1
argc = 2
argv[0] = genVs2019PrjTemplateFile.exe
argv[1] = D:\my_dev\my_tools\vs2019_prj_template\case\my_gl_exp5d1a
task begin
found file : awesomeface.png
found file : bin_x64_Debug\glfw3.dll
found file : container.jpg
found file : fs.cfg
found file : glad.c
found file : main.cpp
found file : my_gl_exp5d1a.vcxproj
found file : my_gl_exp5d1a.vcxproj.filters
found file : vs.cfg
found file : wall.jpg
task end
工程文件为 - my_gl_exp5d1a.vcxproj
工程名称为 - my_gl_exp5d1a
工程过滤文件为 - my_gl_exp5d1a.vcxproj.filters
工程模板中包含的其他文件数量为8个
[成功] 保存VS2019工程模板配置文件 D:\my_dev\my_tools\vs2019_prj_template\case\my_gl_exp5d1a\my_gl_exp5d1a.vstemplate
[成功]
请全选工程根目录下的所有文件
右击 >> 压缩 >> zip文件
该.zip文件为VS2019工程模板
将此.zip放到VS2019的工程模板目录中(e.g. C:\Users\usr_name\Documents\Visual Studio 2019\Templates\ProjectTemplates\)
用VS2019新建工程, 选择"所有语言/所有平台/所有项目类型", 搜索自己模板描述的关键字(e.g. vs2019), 就可以找到自己的工程模板
然后就可以用自己的工程模板新建工程了, enjoy :-P
process files(and dirs) cnt = 10
处理成功
END
D:\my_dev\my_tools\vs2019_prj_template\case>
工程实现
工程除了用到tinyxml2.6.2, 其他实现都在一个genVs2019PrjTemplateFile.cpp中
genVs2019PrjTemplateFile.cpp
500行的实现,花了2天
// @file genVs2019PrjTemplateFile.cpp
// @brief 在给定VS2019工程文件夹中创建vs2019工程模板文件 t.vstemplate
// @usage THE_EXE vs2019_prj_dir
// @note 程序需要的参数, 全部从给定的工程文件夹中提取
#include <iostream>
#include <sstream>
#include <io.h>
#include <string>
#include <vector>
// #include "tinyxml.h"
#include "tinyxml/tinyxml.h"
#define PROG_VER "0.1"
struct TAG_PARAM; // 前向声明
bool callback_my_fn(TAG_PARAM& param);
void do_task_begin(TAG_PARAM& param);
void do_task_ing(TAG_PARAM& param);
void do_task_end(TAG_PARAM& param);
typedef bool (*PFN_CALLBACK_MY_FN)(TAG_PARAM& param);
struct TAG_PARAM {
std::string str_gen_vs2019_prj_template_cfg_file_path_name;
std::string str_prj_name; // 工程名称
std::string str_prj_file_name; // 工程文件名称 x.vcxproj
std::string str_prj_filters_file_name;// 工程过滤文件名称 x.vcxproj.filters
std::vector<std::string> vec_files_was_found; // 最终找到的有效文件
int iCntTotal; // 遍历后,进入回调的文件数量
bool is_task_begin; // 任务开始
bool is_task_end; // 任务结束
bool is_file_or_dir; // true = file, false = dir
std::string strCurFileOrDir;
std::string strRelativePath; // 相对文件路径,用于写入vs2019模板配置文件
// 工程根目录
// e.g. x:\\dir_top
std::string str_prj_root_dir;
// 工程根目录下的垃圾目录
// .vs
// debug
// my_res\\temp_dir1
std::vector<std::string> vec_str_trush_dirs;
// 工程根目录下的垃圾文件
// temp1.txt
// temp_dir\\temp2.txt
std::vector<std::string> vec_str_trush_files;
PFN_CALLBACK_MY_FN pfn_cb;
};
void show_cmdline_param(int argc, char** argv);
bool is_ok_cmdline_param(int argc, char** argv);
void usage();
bool process_dir(char* pszPath);
bool find_all_files_in_dir(char* pszPath, TAG_PARAM& param);
bool is_obj_exist(std::string& str_root_path, const std::vector<std::string> vec, std::string str_to_find);
std::string sanitize_path_separator(std::string str_path); // 规范路径末尾的分隔符
std::string remove_root_path(std::string str_full_path, std::string str_root_path);
bool reverse_find(std::string str, std::string str_to_find);
bool find_postfix_and_delete(std::vector<std::string>& vec, std::string strPostfix, std::string& strWasFound);
bool get_file_pre_and_postfix(std::string strFilename, std::string& strPrefix, std::string& strPostfix);
std::string get_FileName_from_FilePathName(std::string str_file_path_name);
bool gen_vs2019_prj_template_cfg_file(TAG_PARAM& param);
int main(int argc, char** argv) {
bool b_rc = false;
do {
std::cout << "genVs2019PrjTemplateFile " << PROG_VER << std::endl;
show_cmdline_param(argc, argv);
if (!is_ok_cmdline_param(argc, argv)) {
usage();
break;
}
b_rc = process_dir(argv[1]);
} while (false);
std::cout << "\r\n\r\n";
std::cout << "处理" << (b_rc ? "成功" : "失败") << std::endl;
std::cout << "\r\n\r\n";
std::cout << "END" << std::endl;
return EXIT_SUCCESS;
}
void show_cmdline_param(int argc, char** argv) {
std::cout << "argc = " << argc << std::endl;
for (int i = 0; i < argc; i++) {
std::cout << "argv[" << i << "] = " << argv[i] << std::endl;
}
}
bool is_ok_cmdline_param(int argc, char** argv) {
bool b_rc = false;
do {
if (argc < 2) {
break;
}
b_rc = true;
} while (false);
return b_rc;
}
void usage() {
std::cout << "\r\n\r\n";
std::cout << "USEAGE:" << std::endl;
std::cout << "THE_EXE a_vs2019_project_dir_full_path_name" << std::endl;
std::cout << "备注:" << std::endl;
std::cout << "\t做工程模板配置文件之前, 先将不属于工程的文件/临时文件删干净" << std::endl;
}
bool process_dir(char* pszPath) {
bool b_rc = false;
TAG_PARAM param;
do {
param.str_prj_root_dir = pszPath;
param.vec_str_trush_dirs.push_back(".vs");
param.vec_str_trush_files.push_back("vcxproj.user");
param.vec_str_trush_files.push_back(".sln");
param.is_task_begin = true;
param.is_task_end = false;
param.pfn_cb = callback_my_fn;
if (!param.pfn_cb(param)) {
break;
}
param.is_task_begin = false;
param.is_task_end = false;
if (!find_all_files_in_dir(pszPath, param)) {
break;
}
param.is_task_begin = false;
param.is_task_end = true;
param.pfn_cb = callback_my_fn;
if (!param.pfn_cb(param)) {
break;
}
b_rc = true;
} while (false);
std::cout << "process files(and dirs) cnt = " << param.iCntTotal << std::endl;
return b_rc;
}
// @fn bool find_all_files_in_dir(char* pszPath)
// @param char* pszPath, 要操作的目录名称的全路径
bool find_all_files_in_dir(char* pszPath, TAG_PARAM& param) {
struct _finddata_t fileinfo;
intptr_t hFile = _findfirst((std::string(pszPath) + "\\*.*").c_str(), &fileinfo);
if (hFile == -1) {
std::cout << "find file failed - " << pszPath << std::endl;
return false;
}
do {
// 排除当前目录和父目录
if (strcmp(fileinfo.name, ".") == 0 ||
strcmp(fileinfo.name, "..") == 0) {
continue;
}
std::string fullPath = std::string(pszPath) + "\\" + fileinfo.name;
param.strCurFileOrDir = fullPath;
if (fileinfo.attrib & _A_SUBDIR) { // 子目录递归遍历
param.is_file_or_dir = false;
if (param.pfn_cb(param)) {
find_all_files_in_dir(const_cast<char*>(fullPath.c_str()), param);
}
} else { // 文件处理
param.is_file_or_dir = true;
param.pfn_cb(param);
}
} while (_findnext(hFile, &fileinfo) == 0);
_findclose(hFile);
return true;
}
bool callback_my_fn(TAG_PARAM& param) {
bool b_rc = false;
std::string strRelativePath; // 相对文件路径
do {
if (param.is_task_begin) {
do_task_begin(param);
} else if (param.is_task_end) {
do_task_end(param);
} else {
std::string str_to_find = param.strCurFileOrDir;
if (param.is_file_or_dir) {
// is file
if (!is_obj_exist(param.str_prj_root_dir, param.vec_str_trush_files, str_to_find)) {
// 不是垃圾文件
// std::cout << "file : " << param.strCurFileOrDir << std::endl;
// 从str_to_find中去掉param.str_prj_root_dir, 得到不带路径的相对文件路径(e.g. dir1\\b.txt)
strRelativePath = remove_root_path(str_to_find, param.str_prj_root_dir);
if (strRelativePath.empty()) {
__debugbreak();
}
// 将 strRelativePath 写入VS2019的工程模板配置
param.strRelativePath = strRelativePath;
do_task_ing(param);
} else {
// 是垃圾, 外面不要再遍历
// 对于文件无所谓, 外面不判断回调是否返回false
break;
}
} else if (!param.is_file_or_dir) {
// is dir
if (!is_obj_exist(param.str_prj_root_dir, param.vec_str_trush_dirs, str_to_find)) {
// 不是垃圾文件
// param.iCntTotal++; // 如果是纯目录, 不用做写入VS2019模板配置
// 目录不用写到VS2019模板配置中,只用于调试
// std::cout << "dir : " << param.strCurFileOrDir << std::endl;
} else {
// 是垃圾, 外面不要再遍历
// 外面有判断,如果文件夹遍历时,回调返回false, 将不会再遍历此文件夹
break;
}
}
}
b_rc = true; // 需要继续处理
} while (false);
return b_rc;
}
bool reverse_find(std::string str, std::string str_to_find) {
return str.rfind(str_to_find) != std::string::npos;
}
// 查找str_to_find是否在vec中,请实现
bool is_obj_exist(std::string& str_root_path, const std::vector<std::string> vec, std::string str_to_find) {
std::vector<std::string>::const_iterator it;
std::string str_full_path;
bool b_find = false;
char c = '\0';
for (it = vec.begin(); it != vec.end(); it++) {
str_full_path = sanitize_path_separator(str_root_path);
str_full_path += *it;
if (std::string::npos != str_to_find.find(str_full_path)) {
// 找到了垃圾
b_find = true;
break;
} else {
// 找 ".sln" 这样的,从后往前找
if (reverse_find(str_to_find, *it)) {
b_find = true;
break;
}
}
}
return b_find;
}
std::string sanitize_path_separator(std::string str_path) {
std::string str_full_path = str_path;
char c = str_full_path.back();
if (('/' != c) && ('\\' != c)) {
str_full_path += '\\';
}
return str_full_path;
}
std::string remove_root_path(std::string str_full_path, std::string str_root_path) {
// 检查完整路径是否以根路径开头
if (str_full_path.find(str_root_path) == 0) {
// 获取根路径的长度
size_t root_length = str_root_path.length();
// 检查根路径后是否有路径分隔符
if (root_length < str_full_path.length() && (str_full_path[root_length] == '\\')) {
// 跳过路径分隔符
root_length++;
}
// 返回从根路径之后开始的子字符串
return str_full_path.substr(root_length);
}
// 如果完整路径不以根路径开头,返回空
return "";
}
void do_task_begin(TAG_PARAM& param) {
std::cout << "task begin" << std::endl;
param.iCntTotal = 0;
param.vec_files_was_found.clear();
}
void do_task_ing(TAG_PARAM& param) {
std::cout << "found file : " << param.strRelativePath << std::endl;
param.vec_files_was_found.push_back(param.strRelativePath);
param.iCntTotal++;
}
void do_task_end(TAG_PARAM& param) {
std::cout << "task end" << std::endl;
std::string str_postfix;
do {
// 从param.vec_files_was_found中找到*.vcxproj, 并删除 *。vcproj
if (!find_postfix_and_delete(param.vec_files_was_found, ".vcxproj", param.str_prj_file_name)) {
std::cout << "error - 没有找到工程文件 *.vcxproj" << std::endl;
break;
}
std::cout << "工程文件为 - " << param.str_prj_file_name << std::endl;
if (!get_file_pre_and_postfix(param.str_prj_file_name, param.str_prj_name, str_postfix)) {
std::cout << "error 没有找到工程名称 - " << param.str_prj_file_name << std::endl;
break;
}
std::cout << "工程名称为 - " << param.str_prj_name << std::endl;
param.str_prj_filters_file_name.clear();
if (!find_postfix_and_delete(param.vec_files_was_found, ".vcxproj.filters", param.str_prj_filters_file_name)) {
std::cout << "warning - 没有找到工程过滤文件 .vcxproj.filters" << std::endl;
}
std::cout << "工程过滤文件为 - " << param.str_prj_filters_file_name << std::endl;
std::cout << "工程模板中包含的其他文件数量为" << param.vec_files_was_found.size() << "个" << std::endl;
if (!gen_vs2019_prj_template_cfg_file(param)) {
break;
}
std::cout << "[成功]\r\n"
<< "请全选工程根目录下的所有文件\r\n"
<< "右击 >> 压缩 >> zip文件\r\n"
<< "该.zip文件为VS2019工程模板\r\n"
<< "将此.zip放到VS2019的工程模板目录中(e.g. C:\\Users\\usr_name\\Documents\\Visual Studio 2019\\Templates\\ProjectTemplates\\)\r\n"
<< "用VS2019新建工程, 选择\"所有语言/所有平台/所有项目类型\", 搜索自己模板描述的关键字(e.g. vs2019), 就可以找到自己的工程模板\r\n"
<< "然后就可以用自己的工程模板新建工程了, enjoy :-P"
<< std::endl;
} while (false);
}
bool gen_vs2019_prj_template_cfg_file(TAG_PARAM& param) {
bool b_rc = false;
std::string strTemp;
std::stringstream ss;
std::vector<std::string>::const_iterator it;
do {
param.str_gen_vs2019_prj_template_cfg_file_path_name = sanitize_path_separator(param.str_prj_root_dir);
param.str_gen_vs2019_prj_template_cfg_file_path_name += param.str_prj_name;
param.str_gen_vs2019_prj_template_cfg_file_path_name += ".vstemplate";
// 创建 XML 文档对象
TiXmlDocument doc;
// <VSTemplate Version="3.0.0" xmlns="http://schemas.microsoft.com/developer/vstemplate/2005" Type="Project">
TiXmlElement* root = new TiXmlElement("VSTemplate");
root->SetAttribute("Version", "3.0.0");
root->SetAttribute("xmlns", "http://schemas.microsoft.com/developer/vstemplate/2005");
root->SetAttribute("Type", "Project");
doc.LinkEndChild(root);
// <TemplateData>
TiXmlElement* templateData = new TiXmlElement("TemplateData");
root->LinkEndChild(templateData);
// <Name>ConsoleApplication1</Name>
TiXmlElement* name = new TiXmlElement("Name");
TiXmlText* nameText = new TiXmlText(param.str_prj_name.data());
name->LinkEndChild(nameText);
templateData->LinkEndChild(name);
// <Description>my prj template</Description>
TiXmlElement* description = new TiXmlElement("Description");
ss << "my vs2019 prj template - " << param.str_prj_name;
strTemp = ss.str();
ss.str("");
ss.clear();
TiXmlText* descriptionText = new TiXmlText(strTemp.data());
description->LinkEndChild(descriptionText);
templateData->LinkEndChild(description);
// <ProjectType>VC</ProjectType>
TiXmlElement* projectType = new TiXmlElement("ProjectType");
TiXmlText* projectTypeText = new TiXmlText("VC");
projectType->LinkEndChild(projectTypeText);
templateData->LinkEndChild(projectType);
// <ProjectSubType></ProjectSubType>
TiXmlElement* projectSubType = new TiXmlElement("ProjectSubType");
templateData->LinkEndChild(projectSubType);
// <SortOrder>1000</SortOrder>
TiXmlElement* sortOrder = new TiXmlElement("SortOrder");
TiXmlText* sortOrderText = new TiXmlText("1000");
sortOrder->LinkEndChild(sortOrderText);
templateData->LinkEndChild(sortOrder);
// <CreateNewFolder>true</CreateNewFolder>
TiXmlElement* createNewFolder = new TiXmlElement("CreateNewFolder");
TiXmlText* createNewFolderText = new TiXmlText("true");
createNewFolder->LinkEndChild(createNewFolderText);
templateData->LinkEndChild(createNewFolder);
// <DefaultName>ConsoleApplication1</DefaultName>
TiXmlElement* defaultName = new TiXmlElement("DefaultName");
TiXmlText* defaultNameText = new TiXmlText(param.str_prj_name.data());
defaultName->LinkEndChild(defaultNameText);
templateData->LinkEndChild(defaultName);
// <ProvideDefaultName>true</ProvideDefaultName>
TiXmlElement* provideDefaultName = new TiXmlElement("ProvideDefaultName");
TiXmlText* provideDefaultNameText = new TiXmlText("true");
provideDefaultName->LinkEndChild(provideDefaultNameText);
templateData->LinkEndChild(provideDefaultName);
// <LocationField>Enabled</LocationField>
TiXmlElement* locationField = new TiXmlElement("LocationField");
TiXmlText* locationFieldText = new TiXmlText("Enabled");
locationField->LinkEndChild(locationFieldText);
templateData->LinkEndChild(locationField);
// <EnableLocationBrowseButton>true</EnableLocationBrowseButton>
TiXmlElement* enableLocationBrowseButton = new TiXmlElement("EnableLocationBrowseButton");
TiXmlText* enableLocationBrowseButtonText = new TiXmlText("true");
enableLocationBrowseButton->LinkEndChild(enableLocationBrowseButtonText);
templateData->LinkEndChild(enableLocationBrowseButton);
// <Icon>__TemplateIcon.ico</Icon>
TiXmlElement* icon = new TiXmlElement("Icon");
// TiXmlText* iconText = new TiXmlText("__TemplateIcon.ico");
// icon->LinkEndChild(iconText);
templateData->LinkEndChild(icon);
// <TemplateContent>
TiXmlElement* templateContent = new TiXmlElement("TemplateContent");
root->LinkEndChild(templateContent);
// <Project TargetFileName="ConsoleApplication1.vcxproj" File="ConsoleApplication1.vcxproj" ReplaceParameters="true">
TiXmlElement* project = new TiXmlElement("Project");
project->SetAttribute("TargetFileName", param.str_prj_file_name.data());
project->SetAttribute("File", param.str_prj_file_name.data());
project->SetAttribute("ReplaceParameters", "true");
templateContent->LinkEndChild(project);
// 添加工程过滤文件
// <ProjectItem ReplaceParameters="false" TargetFileName="$projectname$.vcxproj.filters">ConsoleApplication1.vcxproj.filters</ProjectItem>
if (!param.str_prj_filters_file_name.empty()) {
TiXmlElement* projectItem1 = new TiXmlElement("ProjectItem");
projectItem1->SetAttribute("ReplaceParameters", "false");
projectItem1->SetAttribute("TargetFileName", "$projectname$.vcxproj.filters");
TiXmlText* projectItem1Text = new TiXmlText(param.str_prj_filters_file_name.data());
projectItem1->LinkEndChild(projectItem1Text);
project->LinkEndChild(projectItem1);
}
// 循环添加 param.vec_files_was_found 中的工程目录中的其他文件
for (it = param.vec_files_was_found.begin(); it != param.vec_files_was_found.end(); it++) {
// <ProjectItem ReplaceParameters="false" TargetFileName="ConsoleApplication1.cpp">ConsoleApplication1.cpp</ProjectItem>
// <ProjectItem ReplaceParameters="false" TargetFileName="glfw3.dll">BIN_X64_DEBUG\glfw3.dll</ProjectItem>
TiXmlElement* projectItem1 = new TiXmlElement("ProjectItem");
projectItem1->SetAttribute("ReplaceParameters", "false");
strTemp = *it;
// TargetFileName的值必须是不带路径的文件名(e.g. "test.dat"), 否则无法出现在VS2019工程中
projectItem1->SetAttribute("TargetFileName", get_FileName_from_FilePathName(strTemp).data());
// ProjectItem的值是相对路径名称(e.g. BIN_X64_DEBUG\glfw3.dll)
TiXmlText* projectItem1Text = new TiXmlText(strTemp.data());
projectItem1->LinkEndChild(projectItem1Text);
project->LinkEndChild(projectItem1);
}
// 保存VS2019工程模板配置文件到工程的根目录
b_rc = doc.SaveFile(param.str_gen_vs2019_prj_template_cfg_file_path_name.data());
std::cout << "[" << (b_rc ? "成功" : "失败") << "] 保存VS2019工程模板配置文件 " <<
param.str_gen_vs2019_prj_template_cfg_file_path_name << std::endl;
} while (false);
return b_rc;
}
bool get_file_pre_and_postfix(std::string strFilename, std::string& strPrefix, std::string& strPostfix) {
// 从文件名strFilename中取得文件前缀名称strPrefix和文件后缀名称strPostfix
// 测试用例
// strFilename = "test.dat";
// 分隔符号为 '.'
// strPrefix = "test";
// strPostfix = "dat";
// 如果有分隔符号'.', 并取得了前后缀, 返回true; 否则返回false
// 查找分隔符号 '.' 的位置
size_t dotPos = strFilename.find('.');
// 如果找到了分隔符号 '.'
if (dotPos != std::string::npos) {
// 提取文件前缀
strPrefix = strFilename.substr(0, dotPos);
// 提取文件后缀
strPostfix = strFilename.substr(dotPos + 1);
return true;
}
return false;
}
bool find_postfix_and_delete(std::vector<std::string>& vec, std::string strPostfix, std::string& strWasFound) {
// 从 vec 中找到 后缀为 strPostfix的文件,并赋值到 strWasFound, 并返回true
// 如果没有找到,返回false
// 测试用例 :
// vec中有一个项为 "data\\a.proj"
// strPostfix 为 ".proj"
// 函数返回true, strWasFound = "data\\a.proj";
for (auto it = vec.begin(); it != vec.end(); ++it) {
const std::string& current = *it;
if (current.length() >= strPostfix.length() &&
current.compare(current.length() - strPostfix.length(), strPostfix.length(), strPostfix) == 0) {
strWasFound = current;
vec.erase(it);
return true;
}
}
return false;
}
std::string get_FileName_from_FilePathName(std::string str_file_path_name) {
// 从'全路径文件名称'或者'文件相对路径名称'中, 得到不带路径的文件名称
// 测试用例
// str_file_path_name = "d:\\my_temp\\test.dat";
// str_file_path_name = "my_temp\\test.dat"
// str_file_path_name = ".\test.dat"
// 以上3这种测试用例, 使本函数都返回 "test.dat"
// 查找最后一个路径分隔符的位置,Windows 下是反斜杠 '\',为了在字符串中表示需要转义为 '\\',同时考虑 Unix 下的斜杠 '/'
size_t pos = str_file_path_name.find_last_of("\\/");
if (pos != std::string::npos) {
// 如果找到了路径分隔符,返回分隔符之后的部分,即文件名
return str_file_path_name.substr(pos + 1);
}
// 如果没有找到路径分隔符,说明输入的本身就是文件名
return str_file_path_name;
}