Files
GitVer/GitVer/GitVer.cpp

910 lines
25 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// GitVer.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include "pch.h"
#include "framework.h"
#include "GitVer.h"
#include "gitver_common.h"
#include "gitver_cli.h"
#include "gitver_nuitka.h"
#include "gitver_process.h"
#include "gitver_rewrite.h"
#include "gitver_tag.h"
#include "gitver_types.h"
#include "gitver_version.h"
#include "gitver_setup.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// 唯一的应用程序对象
CWinApp theApp;
using namespace std;
extern TCHAR g_szCurModuleFileName[MAX_PATH] = { 0 };
extern TCHAR g_szCurModuleDir[MAX_PATH] = { 0 };
extern TCHAR g_szFna[_MAX_FNAME] = { 0 };
extern TCHAR g_szExt[_MAX_EXT] = { 0 };
extern TCHAR g_szFolderName[MAX_PATH] = { 0 };
const UINT DEFAULT_MAJOR_WHEN_NO_TAG = 1;
const UINT DEFAULT_MINOR_WHEN_NO_TAG = 0;
int HandleSetVerCommand(int argc, TCHAR* argv[]);
/// <summary>
/// 将宽字符字符串转换为ANSI字符串。
/// </summary>
/// <param name="lpText">要转换的宽字符字符串。</param>
/// <returns>转换后的ANSI字符串。</returns>
std::string ToAnsiString(LPCTSTR lpText)
{
if (lpText == NULL)
{
return std::string();
}
#ifdef UNICODE
int nSize = WideCharToMultiByte(CP_ACP, 0, lpText, -1, NULL, 0, NULL, NULL);
if (nSize <= 1)
{
return std::string();
}
std::string strValue;
strValue.resize(nSize - 1);
WideCharToMultiByte(CP_ACP, 0, lpText, -1, &strValue[0], nSize, NULL, NULL);
return strValue;
#else
return std::string(lpText);
#endif
}
/// <summary>
/// 检查指定目录下是否存在与模式匹配的文件。
/// </summary>
/// <param name="lpBaseDir">要搜索的基目录。</param>
/// <param name="lpPattern">要匹配的文件模式。</param>
/// <returns>如果存在匹配的文件则返回TRUE否则返回FALSE。</returns>
BOOL HasMatchingFile(LPCTSTR lpBaseDir, LPCTSTR lpPattern)
{
CString strSearchPath;
strSearchPath.Format(_T("%s%s"), lpBaseDir, lpPattern);
WIN32_FIND_DATA findData = { 0 };
HANDLE hFind = ::FindFirstFile(strSearchPath, &findData);
if (hFind == INVALID_HANDLE_VALUE)
{
return FALSE;
}
::FindClose(hFind);
return TRUE;
}
/// <summary>
/// 递归查找与模式匹配的第一个文件。
/// </summary>
/// <param name="lpBaseDir">要搜索的基目录。</param>
/// <param name="lpPattern">要匹配的文件模式。</param>
/// <param name="strFoundPath">找到的文件的完整路径。</param>
/// <returns>如果找到匹配的文件则返回TRUE否则返回FALSE。</returns>
BOOL FindFirstFileByPatternRecursive(LPCTSTR lpBaseDir, LPCTSTR lpPattern, CString& strFoundPath)
{
strFoundPath.Empty();
if (lpBaseDir == NULL || lpPattern == NULL)
{
return FALSE;
}
CString strSearchPath;
strSearchPath.Format(_T("%s*"), lpBaseDir);
WIN32_FIND_DATA findData = { 0 };
HANDLE hFind = ::FindFirstFile(strSearchPath, &findData);
if (hFind == INVALID_HANDLE_VALUE)
{
return FALSE;
}
do
{
if (_tcscmp(findData.cFileName, _T(".")) == 0 || _tcscmp(findData.cFileName, _T("..")) == 0)
{
continue;
}
CString strFullPath;
strFullPath.Format(_T("%s%s"), lpBaseDir, findData.cFileName);
if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
if (_tcsicmp(findData.cFileName, _T(".git")) == 0)
{
continue;
}
CString strSubDir = strFullPath + _T("\\");
if (FindFirstFileByPatternRecursive(strSubDir, lpPattern, strFoundPath))
{
::FindClose(hFind);
return TRUE;
}
}
else
{
if (PathMatchSpec(findData.cFileName, lpPattern))
{
strFoundPath = strFullPath;
::FindClose(hFind);
return TRUE;
}
}
} while (::FindNextFile(hFind, &findData));
::FindClose(hFind);
return FALSE;
}
/// <summary>
/// 构建相对于仓库的路径。
/// </summary>
/// <param name="lpBaseDir">基目录。</param>
/// <param name="lpFullPath">完整路径。</param>
/// <param name="strRelativePath">相对路径。</param>
/// <returns>如果成功构建相对路径则返回TRUE否则返回FALSE。</returns>
BOOL BuildRepoRelativePath(LPCTSTR lpBaseDir, LPCTSTR lpFullPath, CString& strRelativePath)
{
strRelativePath.Empty();
if (lpBaseDir == NULL || lpFullPath == NULL)
{
return FALSE;
}
CString strBase = lpBaseDir;
CString strFull = lpFullPath;
if (strBase.IsEmpty() || strFull.IsEmpty())
{
return FALSE;
}
if (strBase.Right(1) != _T("\\"))
{
strBase += _T("\\");
}
if (strFull.GetLength() <= strBase.GetLength())
{
return FALSE;
}
if (strFull.Left(strBase.GetLength()).CompareNoCase(strBase) != 0)
{
return FALSE;
}
strRelativePath = strFull.Mid(strBase.GetLength());
return !strRelativePath.IsEmpty();
}
/// <summary>
/// 获取文件名(不包含扩展名)。
/// </summary>
/// <param name="lpFilePath">文件路径。</param>
/// <returns>不包含扩展名的文件名。</returns>
CString GetFileNameWithoutExtension(LPCTSTR lpFilePath)
{
if (lpFilePath == NULL)
{
return _T("");
}
CString strFileName = PathFindFileName(lpFilePath);
int nDot = strFileName.ReverseFind(_T('.'));
if (nDot > 0)
{
strFileName = strFileName.Left(nDot);
}
return strFileName;
}
/// <summary>
/// 查找首选的C++资源文件(.rc
/// </summary>
/// <param name="lpBaseDir">要搜索的基目录。</param>
/// <param name="strResFile">找到的资源文件的完整路径。</param>
/// <returns>如果找到首选的资源文件则返回TRUE否则返回FALSE。</returns>
BOOL FindPreferredCppRcFileAt(LPCTSTR lpBaseDir, CString& strResFile)
{
strResFile.Empty();
if (lpBaseDir == NULL)
{
return FALSE;
}
CString strBaseDir = lpBaseDir;
if (!strBaseDir.IsEmpty() && strBaseDir.Right(1) != _T("\\"))
{
strBaseDir += _T("\\");
}
CString strBaseName = strBaseDir;
if (!strBaseName.IsEmpty() && strBaseName.Right(1) == _T("\\"))
{
strBaseName.Delete(strBaseName.GetLength() - 1);
}
int nPos = strBaseName.ReverseFind(_T('\\'));
if (nPos >= 0)
{
strBaseName = strBaseName.Mid(nPos + 1);
}
CString strPreferredPath;
strPreferredPath.Format(_T("%s%s.rc"), strBaseDir.GetString(), strBaseName.GetString());
if (PathFileExists(strPreferredPath))
{
strResFile = strPreferredPath;
return TRUE;
}
return FindFirstFileByPatternRecursive(strBaseDir, _T("*.rc"), strResFile);
}
/// <summary>
/// 查找首选的AssemblyInfo.cs文件。
/// </summary>
/// <param name="lpBaseDir">要搜索的基目录。</param>
/// <param name="strResFile">找到的AssemblyInfo.cs文件的完整路径。</param>
/// <returns>如果找到首选的AssemblyInfo.cs文件则返回TRUE否则返回FALSE。</returns>
BOOL FindPreferredAssemblyInfoFileAt(LPCTSTR lpBaseDir, CString& strResFile)
{
strResFile.Empty();
if (lpBaseDir == NULL)
{
return FALSE;
}
CString strBaseDir = lpBaseDir;
if (!strBaseDir.IsEmpty() && strBaseDir.Right(1) != _T("\\"))
{
strBaseDir += _T("\\");
}
CString strPreferredPath = strBaseDir + _T("Properties\\AssemblyInfo.cs");
if (PathFileExists(strPreferredPath))
{
strResFile = strPreferredPath;
return TRUE;
}
return FindFirstFileByPatternRecursive(strBaseDir, _T("AssemblyInfo.cs"), strResFile);
}
/// <summary>
/// 检测项目的源码类型。
/// </summary>
/// <param name="lpBaseDir">要检测的基目录。</param>
/// <returns>返回项目的源码类型枚举值。</returns>
int DetectProjectCodeType(LPCTSTR lpBaseDir)
{
_tprintf(_T("检测项目源码类型:%s\n"), lpBaseDir);
if (PathFileExists(CString(lpBaseDir) + _T("\\Properties\\AssemblyInfo.cs"))
|| HasMatchingFile(lpBaseDir, _T("*.csproj"))
|| HasMatchingFile(lpBaseDir, _T("*.cs")))
{
return PROJECT_CSHARP;
}
if (PathFileExists(CString(lpBaseDir) + _T("pyproject.toml"))
|| PathFileExists(CString(lpBaseDir) + _T("setup.py"))
|| PathFileExists(CString(lpBaseDir) + _T("requirements.txt"))
|| HasMatchingFile(lpBaseDir, _T("*.py")))
{
return PROJECT_PYTHON;
}
if (HasMatchingFile(lpBaseDir, _T("*.vcxproj"))
|| HasMatchingFile(lpBaseDir, _T("*.vcproj"))
|| HasMatchingFile(lpBaseDir, _T("*.sln"))
|| HasMatchingFile(lpBaseDir, _T("*.cpp"))
|| HasMatchingFile(lpBaseDir, _T("*.h"))
|| HasMatchingFile(lpBaseDir, _T("*.rc")))
{
return PROJECT_CPP;
}
return PROJECT_UNKNOWN;
}
/// <summary>
/// 获取项目源码类型的名称。
/// </summary>
/// <param name="nCodeType">项目源码类型枚举值。</param>
/// <returns>项目源码类型的名称。</returns>
LPCTSTR GetProjectCodeTypeName(int nCodeType)
{
switch (nCodeType)
{
case PROJECT_CPP:
return _T("C++");
case PROJECT_CSHARP:
return _T("C#");
case PROJECT_PYTHON:
return _T("Python");
default:
return _T("Unknown");
}
}
/// <summary>
/// 打印完整的使用示例。
/// </summary>
void PrintFullUsageExamples()
{
LPCTSTR lpUsageText =
_T("用法:\n")
_T(" gitver (显示帮助后进入当前分支创建 tag 流程)\n")
_T(" gitver rewrite [PEType可选] [-f可选]\n")
_T(" gitver setver=<pid> [repodir=<path>可选] [-test可选]\n")
_T(" gitver nuitkabuild=<pid> <mainPy> [repodir=<path>可选] [-test可选] [params=\"<nuitka参数>\"可选]\n")
_T(" gitver nuitkapydbuild=<pid> <modulePy> [repodir=<path>可选] [-test可选] [params=\"<nuitka参数>\"可选]\n")
_T(" gitver -setup=0|1 [pid] [repodir=<path>可选] [-test可选]\n")
_T(" -setup=0: 使用 Inno Setup 脚本 (setup.iss)\n")
_T(" -setup=1: 使用 NSIS 脚本 (setup.nsh)\n")
_T("\n示例:\n")
_T(" gitver\n")
_T(" gitver rewrite\n")
_T(" gitver rewrite 2\n")
_T(" gitver rewrite -f\n")
_T(" gitver setver=5 repodir=E:\\Code\\OTH\\gitver\n")
_T(" gitver setver=5\n")
_T(" gitver setver=5 -test\n")
_T(" gitver nuitkabuild=5 main.py\n")
_T(" gitver nuitkabuild=5 main.py -test\n")
_T(" gitver nuitkabuild=5 src\\app.py repodir=E:\\Code\\MyPyProj params=\"--standalone --output-dir=dist\"\n")
_T(" gitver nuitkapydbuild=5 module.py\n")
_T(" gitver nuitkapydbuild=5 module.py -test\n")
_T(" gitver nuitkapydbuild=5 src\\core.py repodir=E:\\Code\\MyPyProj params=\"--output-dir=dist\"\n")
_T(" gitver -setup=0 5\n")
_T(" gitver -setup=1 5 repodir=E:\\Code\\MyProj\n")
_T("params: 传递给 Nuitka 的额外参数,用双引号括起来,如 params=\"--standalone --output-dir=dist\"\n")
_T("-test: 将产品版本号的 major 和 minor 都置为 0用于测试版本构建\n")
_T("无参数时会读取当前分支最近三次 tag并提示选择 major 加 1 或 minor 加 1然后创建新 tag。\n")
_T("未找到当前分支 tag 时自动使用默认版本 1.0。\n");
_tprintf(_T("%s"), lpUsageText);
}
/// <summary>
/// 打印命令简要用法(用于未知命令等场景)。
/// </summary>
void PrintShortCommandUsage()
{
_tprintf(_T("请使用gitver rewrite [PE类型可选]\n"));
_tprintf(_T("请使用gitver setver=<pid> [repodir=<path>可选]\n"));
_tprintf(_T("gitver nuitkabuild=<pid> <mainPy> [repodir=<path>可选] [params=\"<nuitka参数>\"可选]\n"));
_tprintf(_T("gitver nuitkapydbuild=<pid> <modulePy> [repodir=<path>可选] [params=\"<nuitka参数>\"可选]\n"));
}
/// <summary>
/// 打印命令参数不足时的标准用法与示例。
/// </summary>
/// <param name="lpCommandName">命令名称。</param>
/// <param name="lpRequiredArgs">必选参数占位文本。</param>
/// <param name="lpExampleRequiredArgs">示例中的必选参数。</param>
void PrintCommandUsageAndExamples(LPCTSTR lpCommandName, LPCTSTR lpRequiredArgs, LPCTSTR lpExampleRequiredArgs)
{
_tprintf(_T("用法gitver %s %s [repodir=<path>可选]\n"), lpCommandName, lpRequiredArgs);
if (lpExampleRequiredArgs != NULL && _tcslen(lpExampleRequiredArgs) > 0)
{
_tprintf(_T("示例gitver %s 5 %s repodir=E:\\Code\\OTH\\gitver\n"), lpCommandName, lpExampleRequiredArgs);
_tprintf(_T("示例gitver %s 5 %s\n"), lpCommandName, lpExampleRequiredArgs);
}
else
{
_tprintf(_T("示例gitver %s 5 repodir=E:\\Code\\OTH\\gitver\n"), lpCommandName);
_tprintf(_T("示例gitver %s 5\n"), lpCommandName);
}
}
/// <summary>
/// 获取退出码的简要说明。
/// </summary>
LPCTSTR GetExitCodeHint(int nRetCode)
{
switch (nRetCode)
{
case 0:
return _T("success");
case 1:
return _T("mfc init failed");
case 2:
return _T("get commit id failed");
case 3:
return _T("invalid args or unknown command");
case 4:
return _T("setver: invalid pid");
case 5:
return _T("setver: get branch or bid failed");
case 6:
return _T("setver: get tag failed");
case 9:
return _T("setver: get today commit count failed");
case 15:
return _T("unsupported or unknown project type");
case 16:
return _T("rewrite: python project unsupported");
case 17:
return _T("nuitkabuild: missing args");
case 18:
return _T("nuitkabuild: invalid pid");
case 19:
return _T("nuitkabuild: get branch or bid failed");
case 20:
return _T("nuitkabuild: get tag failed");
case 21:
return _T("nuitkabuild: get today commit count failed");
case 22:
return _T("rewrite: invalid pe type");
case 23:
return _T("invalid repo path");
case 24:
return _T("nuitkapydbuild: missing args");
case 25:
return _T("nuitkapydbuild: invalid pid");
case 26:
return _T("nuitkapydbuild: get branch or bid failed");
case 27:
return _T("nuitkapydbuild: get tag failed");
case 28:
return _T("nuitkapydbuild: get today commit count failed");
case 30:
return _T("interactive: get recent tags failed");
case 31:
return _T("interactive: read input failed");
case 32:
return _T("interactive: invalid choice or version overflow");
case 33:
return _T("interactive: create tag failed");
case 34:
return _T("interactive: verify tag failed");
case 35:
return _T("rewrite version file failed");
case 36:
return _T("setup: unsupported -setup value");
case 37:
return _T("setup: setup script not found");
case 38:
return _T("setup: modify setup script failed");
case 39:
return _T("setup: compiler not found");
default:
return _T("");
}
}
/// <summary>
/// 在命令执行返回非零时输出统一错误日志。
/// </summary>
void PrintCommandFailedWithCode(LPCTSTR lpCommandName, int nRetCode)
{
if (nRetCode != 0)
{
LPCTSTR lpHint = GetExitCodeHint(nRetCode);
if (lpHint != NULL && _tcslen(lpHint) > 0)
{
_tprintf(_T("ERROR: command %s failed, code %d (%s)\n"), lpCommandName, nRetCode, lpHint);
}
else
{
_tprintf(_T("ERROR: command %s failed, code %d\n"), lpCommandName, nRetCode);
}
}
}
/// <summary>
/// 输出命令执行结束日志(含耗时和退出码)。
/// </summary>
void LogCommandEnd(LPCTSTR lpCommandName, DWORD dwStartTick, int nRetCode)
{
DWORD dwElapsed = GetTickCount() - dwStartTick;
_tprintf(_T("命令结束: %s耗时 %u ms退出码 %d\n"), lpCommandName, dwElapsed, nRetCode);
}
/// <summary>
/// 获取当前模块的目录信息。
/// </summary>
/// <returns>返回值。</returns>
void GetDirInfo()
{
TCHAR szDrive[_MAX_DRIVE] = { 0 };
TCHAR szDir[_MAX_DIR] = { 0 };
TCHAR szFna[_MAX_FNAME] = { 0 };
TCHAR szExt[_MAX_EXT] = { 0 };
DWORD dwRet = ::GetModuleFileName(NULL, g_szCurModuleFileName, sizeof(g_szCurModuleFileName) / sizeof(TCHAR));
_tsplitpath_s(g_szCurModuleFileName, szDrive, szDir, g_szFna, g_szExt);
_tcscat_s(g_szCurModuleDir, MAX_PATH, szDrive);
_tcscat_s(g_szCurModuleDir, MAX_PATH, szDir);
CString strVal = g_szCurModuleDir;
if (!strVal.IsEmpty() && strVal.GetAt(strVal.GetLength() - 1) == _T('\\'))
{
strVal.Delete(strVal.GetLength() - 1);
_stprintf_s(g_szCurModuleDir, _T("%s"), strVal.GetString());
}
else
{
strVal += _T("\\");
_stprintf_s(g_szCurModuleDir, _T("%s"), strVal.GetString());
}
int nPos = strVal.ReverseFind(_T('\\'));
if (nPos != -1)
{
strVal = strVal.Right(strVal.GetLength() - nPos - 1);
_stprintf_s(g_szFolderName, _T("%s"), strVal.GetString());
}
SetCurrentDirectory(g_szCurModuleDir);
}
/// <summary>
/// 处理设置版本命令。
/// </summary>
/// <param name="argc">参数个数。</param>
/// <param name="argv">参数数组。</param>
/// <returns>返回值。</returns>
int HandleSetVerCommand(int argc, TCHAR* argv[])
{
// 解析 "setver=N" 中的 pid
CString strPid = CString(argv[1]).Mid(7); // 跳过 "setver="
UINT nPid = 0;
if (strPid.IsEmpty() || !TryParseUInt16(strPid, nPid))
{
_tprintf(_T("错误: setver= 后的 pid 无效:%s应为 0-65535 范围的整数。\n"), strPid.GetString());
return 4;
}
LPCTSTR lpRepoPath = NULL;
BOOL bTestMode = FALSE;
int nRepoArgRet = ParseSetVerOptions(argc, argv, 2, lpRepoPath, bTestMode);
if (nRepoArgRet != 0)
{
return nRepoArgRet;
}
CString strProductVersion;
CString strFileVersion;
VersionBuildErrorCodes errorCodes = { 5, 6, 9 };
int nVersionRet = BuildVersionsFromRepo(
lpRepoPath,
nPid,
errorCodes,
strProductVersion,
strFileVersion,
DEFAULT_MAJOR_WHEN_NO_TAG,
DEFAULT_MINOR_WHEN_NO_TAG);
if (nVersionRet != 0)
{
return nVersionRet;
}
if (bTestMode)
{
int nDot1 = strProductVersion.Find(_T('.'));
int nDot2 = (nDot1 >= 0) ? strProductVersion.Find(_T('.'), nDot1 + 1) : -1;
if (nDot2 > nDot1)
{
strProductVersion = strProductVersion.Left(nDot2 + 1) + _T("0.0");
}
_tprintf(_T("[测试版本] 已将 major/minor 置零: ProductVersion=%s\n"), strProductVersion.GetString());
}
_tprintf(_T("ProductVersion=%s\n"), strProductVersion.GetString());
_tprintf(_T("FileVersion=%s\n"), strFileVersion.GetString());
// 检查是否附带了 -setup=N 标志
int nSetupType = -1;
for (int i = 2; i < argc; ++i)
{
CString strArg = argv[i];
if (strArg.GetLength() > 7 && strArg.Left(7).CompareNoCase(_T("-setup=")) == 0)
{
UINT nVal = 0;
if (!TryParseUInt16(strArg.Mid(7), nVal) || (nVal != 0 && nVal != 1))
{
_tprintf(_T("错误: -setup 参数值 \"%s\" 不支持,仅支持 0Inno Setup或 1NSIS\n"),
strArg.Mid(7).GetString());
return 36;
}
nSetupType = (int)nVal;
break;
}
}
if (nSetupType >= 0)
{
// -setup=N 存在:不回写版本信息到项目文件,只修改安装脚本并编译
if (!ExecuteSetupBuild(nSetupType, strProductVersion, strFileVersion))
{
return 38;
}
return 0;
}
return RewriteSetVerByProjectType(lpRepoPath, strProductVersion, strFileVersion);
}
/// <summary>
/// <summary>
/// 准备C++重写内容。
/// </summary>
/// <param name="strCommitId">提交ID。</param>
/// <param name="nPEType">PE类型。</param>
/// <param name="strResFile">资源文件路径。</param>
/// <param name="vtOldContent">旧内容向量。</param>
/// <param name="vtNewContent">新内容向量。</param>
/// </summary>
void PrepareCppRewriteContent(const CString& strCommitId, int nPEType, CString& strResFile, std::vector<std::string>& vtOldContent, std::vector<std::string>& vtNewContent)
{
TCHAR szValue[MAX_PATH] = { 0 };
if (!FindPreferredCppRcFileAt(g_szCurModuleDir, strResFile))
{
_tprintf(_T("错误: 未找到C++ .rc资源文件。\n"));
return;
}
CString strRcRelativePath;
if (BuildRepoRelativePath(g_szCurModuleDir, strResFile, strRcRelativePath))
{
CString strCheckoutCmd;
strCheckoutCmd.Format(_T("cmd /c git checkout -- %s"), QuoteCmdArg(strRcRelativePath).GetString());
StartProcess(NULL, strCheckoutCmd.GetBuffer(), g_szCurModuleDir);
strCheckoutCmd.ReleaseBuffer();
}
#if 0
vtOldContent.push_back(_T("FILEVERSION 1,0,0,1"));
vtOldContent.push_back(_T("VALUE \"FileVersion\", \"1.0.0.1\""));
_stprintf_s(szValue, _T("FILEVERSION 1.0.%s"), strCommitId.GetString());
vtNewContent.push_back(szValue);
_stprintf_s(szValue, _T("VALUE \"FileVersion\", \"1.0.%s\""), strCommitId.GetString());
vtNewContent.push_back(szValue);
#endif
#if 1
CString strModuleName = GetFileNameWithoutExtension(strResFile);
if (strModuleName.IsEmpty())
{
strModuleName = g_szFolderName;
}
_stprintf_s(szValue, _T("VALUE \"OriginalFilename\", \"%s.%s\""), strModuleName.GetString(), nPEType == 1 ? _T("exe") : _T("dll"));
vtOldContent.push_back(ToAnsiString(szValue));
_tprintf(_T("Rewrite old token: %s\n"), szValue);
_stprintf_s(szValue, _T("VALUE \"OriginalFilename\", \"%s\""), strCommitId.GetString());
vtNewContent.push_back(ToAnsiString(szValue));
_tprintf(_T("Rewrite new token: %s\n"), szValue);
#if 0
_stprintf_s(szValue, "VALUE \"FileDescription\", \"TODO: <文件说明>\"");
vtOldContent.push_back(szValue);
_stprintf_s(szValue, "VALUE \"FileDescription\", \"%s\"", strCommitId.GetString());
vtNewContent.push_back(szValue);
#endif
#endif
}
/// <summary>
/// 准备C#重写内容。
/// </summary>
/// <param name="strCommitId">提交ID。</param>
/// <param name="strResFile">资源文件路径。</param>
/// <param name="vtOldContent">旧内容向量。</param>
/// <param name="vtNewContent">新内容向量。</param>
void PrepareCSharpRewriteContent(const CString& strCommitId, CString& strResFile, std::vector<std::string>& vtOldContent, std::vector<std::string>& vtNewContent)
{
TCHAR szValue[MAX_PATH] = { 0 };
if (!FindPreferredAssemblyInfoFileAt(g_szCurModuleDir, strResFile))
{
_tprintf(_T("错误: 未找到首选的 AssemblyInfo 文件。\n"));
return;
}
CString strAssemblyRelativePath;
if (BuildRepoRelativePath(g_szCurModuleDir, strResFile, strAssemblyRelativePath))
{
CString strCheckoutCmd;
strCheckoutCmd.Format(_T("cmd /c git checkout -- %s"), QuoteCmdArg(strAssemblyRelativePath).GetString());
StartProcess(NULL, strCheckoutCmd.GetBuffer(), g_szCurModuleDir);
strCheckoutCmd.ReleaseBuffer();
}
#if 0
vtOldContent.push_back(_T("[assembly: AssemblyFileVersion(\"1.0.0.1\")]"));
_stprintf_s(szValue, _T("[assembly: AssemblyFileVersion(\"1.0.0.%s\")]"), strCommitId.GetString());
vtNewContent.push_back(szValue);
#endif
vtOldContent.push_back("[assembly: AssemblyCopyright(\"\")]");
_stprintf_s(szValue, _T("[assembly: AssemblyCopyright(\"%s\")]"), strCommitId.GetString());
vtNewContent.push_back(ToAnsiString(szValue));
}
/// <summary>
/// 处理重写命令。
/// </summary>
/// <param name="argc">参数个数。</param>
/// <param name="argv">参数数组。</param>
/// <returns>返回值。</returns>
int HandleRewriteCommand(int argc, TCHAR* argv[])
{
int nCodeType = DetectProjectCodeType(g_szCurModuleDir);
int nPEType = 1;
BOOL bForceRewrite = FALSE;
int nRewriteOptRet = ParseRewriteOptions(argc, argv, nPEType, bForceRewrite);
if (nRewriteOptRet != 0)
{
return nRewriteOptRet;
}
if (nCodeType == PROJECT_UNKNOWN)
{
_tprintf(_T("错误: 未知项目类型。\n"));
return 15;
}
_tprintf(_T("项目类型: %s\n"), GetProjectCodeTypeName(nCodeType));
CString strValue = StartProcess(NULL, _T("cmd /c git rev-parse --short HEAD"), NULL);
strValue.Replace(_T("\n"), _T(""));
strValue.Replace(_T("\r"), _T(""));
strValue.Trim();
if (strValue.IsEmpty())
{
_tprintf(_T("错误: 获取提交ID失败。\n"));
return 2;
}
CString strResFile;
std::vector<std::string> vtOldContent;
std::vector<std::string> vtNewContent;
if (nCodeType == PROJECT_CPP)
{
PrepareCppRewriteContent(strValue, nPEType, strResFile, vtOldContent, vtNewContent);
}
else if (nCodeType == PROJECT_CSHARP)
{
PrepareCSharpRewriteContent(strValue, strResFile, vtOldContent, vtNewContent);
}
else if (nCodeType == PROJECT_PYTHON)
{
_tprintf(_T("错误: 不支持的项目类型: Python。\n"));
return 16;
}
if (strResFile.IsEmpty())
{
if (bForceRewrite)
{
_tprintf(_T("警告: 资源文件路径为空,已按 -f 忽略本次重写失败。\n"));
return 0;
}
_tprintf(_T("错误: 资源文件路径为空。\n"));
return 35;
}
if (!ReplaceFileContent(strResFile, vtOldContent, vtNewContent))
{
if (bForceRewrite)
{
_tprintf(_T("警告: 替换文件内容失败,已按 -f 忽略本次重写失败。\n"));
return 0;
}
return 35;
}
return 0;
}
int main(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0;
// 添加图标;
HWND hwnd = GetForegroundWindow();
SendMessage(hwnd, WM_SETICON, ICON_SMALL, (LPARAM)LoadIcon(NULL, MAKEINTRESOURCE(IDI_ICON_APP)));
HMODULE hModule = ::GetModuleHandle(NULL);
GetDirInfo();
if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
{
_tprintf(_T("错误MFC 初始化失败。\n"));
nRetCode = 1;
}
else
{
CString strArgs = BuildArgsForLog(argc, argv);
_tprintf(_T("命令参数: %s\n"), strArgs.GetString());
if (argc <= 1)
{
_tprintf(_T("进入交互式标签创建模式。\n"));
PrintFullUsageExamples();
DWORD dwStartTick = LogCommandStart(_T("interactive"));
int nCmdRet = HandleCreateTagInteractive();
LogCommandEnd(_T("interactive"), dwStartTick, nCmdRet);
PrintCommandFailedWithCode(_T("interactive"), nCmdRet);
// 暂停便于查看输出日志Windows 控制台)
system("pause");
return nCmdRet;
}
if (argc >= 2 && _tcsnicmp(argv[1], _T("nuitkabuild="), 12) == 0)
{
DWORD dwStartTick = LogCommandStart(_T("nuitkabuild"));
int nCmdRet = HandleNuitkaBuildCommand(argc, argv);
LogCommandEnd(_T("nuitkabuild"), dwStartTick, nCmdRet);
PrintCommandFailedWithCode(_T("nuitkabuild"), nCmdRet);
return nCmdRet;
}
if (argc >= 2 && _tcsnicmp(argv[1], _T("nuitkapydbuild="), 15) == 0)
{
DWORD dwStartTick = LogCommandStart(_T("nuitkapydbuild"));
int nCmdRet = HandleNuitkaPydBuildCommand(argc, argv);
LogCommandEnd(_T("nuitkapydbuild"), dwStartTick, nCmdRet);
PrintCommandFailedWithCode(_T("nuitkapydbuild"), nCmdRet);
return nCmdRet;
}
if (argc >= 2 && _tcsnicmp(argv[1], _T("setver="), 7) == 0)
{
DWORD dwStartTick = LogCommandStart(_T("setver"));
int nCmdRet = HandleSetVerCommand(argc, argv);
LogCommandEnd(_T("setver"), dwStartTick, nCmdRet);
PrintCommandFailedWithCode(_T("setver"), nCmdRet);
return nCmdRet;
}
if (argc >= 2 && _tcsicmp(argv[1], _T("rewrite")) == 0)
{
DWORD dwStartTick = LogCommandStart(_T("rewrite"));
int nCmdRet = HandleRewriteCommand(argc - 1, argv + 1);
LogCommandEnd(_T("rewrite"), dwStartTick, nCmdRet);
PrintCommandFailedWithCode(_T("rewrite"), nCmdRet);
return nCmdRet;
}
if (argc >= 2 && _tcsnicmp(argv[1], _T("-setup="), 7) == 0)
{
DWORD dwStartTick = LogCommandStart(_T("-setup"));
int nCmdRet = HandleSetupCommand(argc, argv);
LogCommandEnd(_T("-setup"), dwStartTick, nCmdRet);
PrintCommandFailedWithCode(_T("-setup"), nCmdRet);
return nCmdRet;
}
_tprintf(_T("错误:未知命令:%s\n"), argv[1]);
PrintShortCommandUsage();
return 3;
}
return nRetCode;
}