VS2019 - 修正导出项目的工程模板的错误

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的工程模板,这样才是正道。
但是,如果按照商业化程序的做法来写这个工具,明显付出和收获不成正比。
在平衡了付出和收获的平衡点之后,采用的方案如下:

  1. 将选定工程的垃圾都去掉
  2. 手搓命令行工具,对选定的目录生成VS2019适配的.vstemplate, 且这个.vstemplate就在选定工程的根目录生成。
  3. 将工程根目录的全部东西都选中(包括.vstemplate),右击,用7zip(或者其他zip工具)打包成.zip(名称随便,只要不和现有的工程模板.zip重名就行,且文件后缀是.zip)
  4. 将自己做的工程模板.zip丢到VS2019的工程模板目录下
  5. 新建工程时,输入自己工程模板描述中的关键字(e.g. 2019), 就能找到自己的工程模板,然后就可以从自己的工程模板新建自己的工程了。
  6. 这种方法,特别适合于做一个连续迭代的实验.

笔记

花了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;
}

END

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值