1、加入实现代码

2、配置项目
This commit is contained in:
Jeff
2026-04-23 14:55:44 +08:00
parent 40aaa538f9
commit 89f53efb33
27 changed files with 2611 additions and 25 deletions

View File

@@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17 # Visual Studio Version 17
VisualStudioVersion = 17.14.37203.1 d17.14 VisualStudioVersion = 17.14.37203.1 d17.14
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "GitVer", "GitVer\GitVer.vcxproj", "{8C09E04E-D13C-4D8F-86E1-3B2A712FA540}" Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "GitVer", "GitVer\GitVer.vcxproj", "{480198B5-83D1-4EBB-85BB-FBA5F8A0061E}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -13,19 +13,19 @@ Global
Release|x86 = Release|x86 Release|x86 = Release|x86
EndGlobalSection EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution GlobalSection(ProjectConfigurationPlatforms) = postSolution
{8C09E04E-D13C-4D8F-86E1-3B2A712FA540}.Debug|x64.ActiveCfg = Debug|x64 {480198B5-83D1-4EBB-85BB-FBA5F8A0061E}.Debug|x64.ActiveCfg = Debug|x64
{8C09E04E-D13C-4D8F-86E1-3B2A712FA540}.Debug|x64.Build.0 = Debug|x64 {480198B5-83D1-4EBB-85BB-FBA5F8A0061E}.Debug|x64.Build.0 = Debug|x64
{8C09E04E-D13C-4D8F-86E1-3B2A712FA540}.Debug|x86.ActiveCfg = Debug|Win32 {480198B5-83D1-4EBB-85BB-FBA5F8A0061E}.Debug|x86.ActiveCfg = Debug|Win32
{8C09E04E-D13C-4D8F-86E1-3B2A712FA540}.Debug|x86.Build.0 = Debug|Win32 {480198B5-83D1-4EBB-85BB-FBA5F8A0061E}.Debug|x86.Build.0 = Debug|Win32
{8C09E04E-D13C-4D8F-86E1-3B2A712FA540}.Release|x64.ActiveCfg = Release|x64 {480198B5-83D1-4EBB-85BB-FBA5F8A0061E}.Release|x64.ActiveCfg = Release|x64
{8C09E04E-D13C-4D8F-86E1-3B2A712FA540}.Release|x64.Build.0 = Release|x64 {480198B5-83D1-4EBB-85BB-FBA5F8A0061E}.Release|x64.Build.0 = Release|x64
{8C09E04E-D13C-4D8F-86E1-3B2A712FA540}.Release|x86.ActiveCfg = Release|Win32 {480198B5-83D1-4EBB-85BB-FBA5F8A0061E}.Release|x86.ActiveCfg = Release|Win32
{8C09E04E-D13C-4D8F-86E1-3B2A712FA540}.Release|x86.Build.0 = Release|Win32 {480198B5-83D1-4EBB-85BB-FBA5F8A0061E}.Release|x86.Build.0 = Release|Win32
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {4B466BE8-D922-416A-BE35-C3BB9F65B8E7} SolutionGuid = {CD2858C1-027F-4A5B-8470-01D454B48B60}
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal

View File

@@ -1,20 +1,872 @@
// GitVer.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 // GitVer.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
// //
#include <iostream> #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"
int main() #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;
#ifdef DEBUG
#define RC_DIR _T("F:\\Code\\HS\\SHWS_dev\\SHWS\\")
#define RC_NAME _T("SHWS")
#endif
int HandleSetVerCommand(int argc, TCHAR* argv[]);
/// <summary>
/// 将宽字符字符串转换为ANSI字符串。
/// </summary>
/// <param name="lpText">要转换的宽字符字符串。</param>
/// <returns>转换后的ANSI字符串。</returns>
std::string ToAnsiString(LPCTSTR lpText)
{ {
std::cout << "Hello World!\n"; if (lpText == NULL)
{
return std::string();
} }
// 运行程序: Ctrl + F5 或调试 >“开始执行(不调试)”菜单 #ifdef UNICODE
// 调试程序: F5 或调试 >“开始调试”菜单 int nSize = WideCharToMultiByte(CP_ACP, 0, lpText, -1, NULL, 0, NULL, NULL);
if (nSize <= 1)
{
return std::string();
}
// 入门使用技巧: std::string strValue;
// 1. 使用解决方案资源管理器窗口添加/管理文件 strValue.resize(nSize - 1);
// 2. 使用团队资源管理器窗口连接到源代码管理 WideCharToMultiByte(CP_ACP, 0, lpText, -1, &strValue[0], nSize, NULL, NULL);
// 3. 使用输出窗口查看生成输出和其他消息 return strValue;
// 4. 使用错误列表窗口查看错误 #else
// 5. 转到“项目”>“添加新项”以创建新的代码文件,或转到“项目”>“添加现有项”以将现有代码文件添加到项目 return std::string(lpText);
// 6. 将来,若要再次打开此项目,请转到“文件”>“打开”>“项目”并选择 .sln 文件 #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可选]\n")
_T(" gitver setver [pid] [repoPath可选]\n")
_T(" gitver nuitkabuild [pid] [mainPy] [repoPath可选] [nuitka额外参数可选]\n")
_T(" gitver nuitkapydbuild [pid] [modulePy] [repoPath可选] [nuitka额外参数可选]\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 E:\\Code\\OTH\\gitver\n")
_T(" gitver setver 5\n")
_T(" gitver setver 5 -f\n")
_T(" gitver nuitkabuild 5 main.py\n")
_T(" gitver nuitkabuild 5 main.py -f\n")
_T(" gitver nuitkabuild 5 src\\app.py E:\\Code\\MyPyProj --standalone --output-dir=dist\n")
_T(" gitver nuitkabuild 5 main.py --standalone -f\n")
_T(" gitver nuitkapydbuild 5 module.py\n")
_T(" gitver nuitkapydbuild 5 module.py -f\n")
_T(" gitver nuitkapydbuild 5 src\\core.py E:\\Code\\MyPyProj --output-dir=dist\n")
_T("\n说明: nuitkabuild/nuitkapydbuild 中,-f 在额外参数开始前表示 gitver 选项;进入额外参数后会透传给 Nuitka。\n")
_T("无参数时会读取当前分支最近三次 tag并提示选择 major 加 1 或 minor 加 1然后创建新 tag。\n");
_tprintf(_T("%s"), lpUsageText);
}
/// <summary>
/// 打印命令简要用法(用于未知命令等场景)。
/// </summary>
void PrintShortCommandUsage()
{
_tprintf(_T("请使用gitver rewrite [PE类型可选]\n"));
_tprintf(_T("请使用gitver setver [pid] [repoPath可选]\n"));
_tprintf(_T("gitver nuitkabuild [pid] [mainPy] [repoPath可选] [nuitka额外参数可选]\n"));
_tprintf(_T("gitver nuitkapydbuild [pid] [modulePy] [repoPath可选] [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 [repoPath可选]\n"), lpCommandName, lpRequiredArgs);
if (lpExampleRequiredArgs != NULL && _tcslen(lpExampleRequiredArgs) > 0)
{
_tprintf(_T("示例gitver %s 5 %s E:\\Code\\OTH\\gitver\n"), lpCommandName, lpExampleRequiredArgs);
_tprintf(_T("示例gitver %s 5 %s\n"), lpCommandName, lpExampleRequiredArgs);
}
else
{
_tprintf(_T("示例gitver %s 5 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");
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());
}
#ifdef _DEBUG
_stprintf_s(g_szFolderName, RC_NAME);
_stprintf_s(g_szCurModuleDir, RC_DIR);
#endif
SetCurrentDirectory(g_szCurModuleDir);
}
/// <summary>
/// 处理设置版本命令。
/// </summary>
/// <param name="argc">参数个数。</param>
/// <param name="argv">参数数组。</param>
/// <returns>返回值。</returns>
int HandleSetVerCommand(int argc, TCHAR* argv[])
{
if (argc < 3)
{
_tprintf(_T("错误: 参数不足。\n"));
PrintCommandUsageAndExamples(_T("setver"), _T("[pid]"), _T(""));
_tprintf(_T("参数说明:\n"));
_tprintf(_T(" pid: 产品ID必填范围0-65535。\n"));
_tprintf(_T(" repoPath: 仓库路径,可选,默认使用当前目录。\n"));
return 3;
}
UINT nPid = 0;
int nArgRet = ParseUInt16ArgOrError(argv, 2, _T("pid"), nPid, 4);
if (nArgRet != 0)
{
return nArgRet;
}
LPCTSTR lpRepoPath = NULL;
BOOL bUseDefaultTagWhenMissing = FALSE;
int nRepoArgRet = ParseSetVerOptions(argc, argv, lpRepoPath, bUseDefaultTagWhenMissing);
if (nRepoArgRet != 0)
{
return nRepoArgRet;
}
CString strProductVersion;
CString strFileVersion;
VersionBuildErrorCodes errorCodes = { 5, 6, 9 };
int nVersionRet = BuildVersionsFromRepo(
lpRepoPath,
nPid,
errorCodes,
strProductVersion,
strFileVersion,
bUseDefaultTagWhenMissing,
DEFAULT_MAJOR_WHEN_NO_TAG,
DEFAULT_MINOR_WHEN_NO_TAG);
if (nVersionRet != 0)
{
return nVersionRet;
}
_tprintf(_T("ProductVersion=%s\n"), strProductVersion.GetString());
_tprintf(_T("FileVersion=%s\n"), strFileVersion.GetString());
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));
#ifdef _DEBUG
CString strValue = StartProcess(NULL, _T("cmd /c git rev-parse --short HEAD"), RC_DIR);
#else
CString strValue = StartProcess(NULL, _T("cmd /c git rev-parse --short HEAD"), NULL);
#endif
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);
return nCmdRet;
}
if (argc >= 2 && _tcsicmp(argv[1], _T("nuitkabuild")) == 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 && _tcsicmp(argv[1], _T("nuitkapydbuild")) == 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 && _tcsicmp(argv[1], _T("setver")) == 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;
}
_tprintf(_T("错误:未知命令:%s\n"), argv[1]);
PrintShortCommandUsage();
return 3;
}
return nRetCode;
}

3
GitVer/GitVer.h Normal file
View File

@@ -0,0 +1,3 @@
#pragma once
#include "resource.h"

BIN
GitVer/GitVer.rc Normal file

Binary file not shown.

View File

@@ -21,7 +21,7 @@
<PropertyGroup Label="Globals"> <PropertyGroup Label="Globals">
<VCProjectVersion>17.0</VCProjectVersion> <VCProjectVersion>17.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword> <Keyword>Win32Proj</Keyword>
<ProjectGuid>{8c09e04e-d13c-4d8f-86e1-3b2a712fa540}</ProjectGuid> <ProjectGuid>{480198b5-83d1-4ebb-85bb-fba5f8a0061e}</ProjectGuid>
<RootNamespace>GitVer</RootNamespace> <RootNamespace>GitVer</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion> <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup> </PropertyGroup>
@@ -31,6 +31,7 @@
<UseDebugLibraries>true</UseDebugLibraries> <UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset> <PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet> <CharacterSet>Unicode</CharacterSet>
<UseOfMfc>Dynamic</UseOfMfc>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType> <ConfigurationType>Application</ConfigurationType>
@@ -38,19 +39,22 @@
<PlatformToolset>v143</PlatformToolset> <PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization> <WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet> <CharacterSet>Unicode</CharacterSet>
<UseOfMfc>Dynamic</UseOfMfc>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType> <ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries> <UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset> <PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet> <CharacterSet>MultiByte</CharacterSet>
<UseOfMfc>Dynamic</UseOfMfc>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType> <ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries> <UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset> <PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization> <WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet> <CharacterSet>MultiByte</CharacterSet>
<UseOfMfc>Static</UseOfMfc>
</PropertyGroup> </PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings"> <ImportGroup Label="ExtensionSettings">
@@ -70,12 +74,23 @@
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup> </ImportGroup>
<PropertyGroup Label="UserMacros" /> <PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<OutDir>..\..\..\..\bin\$(ProjectName)\</OutDir>
<IntDir>$(OutDir)$(Configuration)\</IntDir>
<TargetName>$(ProjectName)d</TargetName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<OutDir>..\..\..\..\bin\$(ProjectName)\</OutDir>
<IntDir>$(OutDir)$(Configuration)\</IntDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile> <ClCompile>
<WarningLevel>Level3</WarningLevel> <WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck> <SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode> <ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
</ClCompile> </ClCompile>
<Link> <Link>
<SubSystem>Console</SubSystem> <SubSystem>Console</SubSystem>
@@ -90,6 +105,8 @@
<SDLCheck>true</SDLCheck> <SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode> <ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
</ClCompile> </ClCompile>
<Link> <Link>
<SubSystem>Console</SubSystem> <SubSystem>Console</SubSystem>
@@ -102,6 +119,8 @@
<SDLCheck>true</SDLCheck> <SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode> <ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
</ClCompile> </ClCompile>
<Link> <Link>
<SubSystem>Console</SubSystem> <SubSystem>Console</SubSystem>
@@ -116,14 +135,50 @@
<SDLCheck>true</SDLCheck> <SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode> <ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
</ClCompile> </ClCompile>
<Link> <Link>
<SubSystem>Console</SubSystem> <SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation> <GenerateDebugInformation>true</GenerateDebugInformation>
</Link> </Link>
</ItemDefinitionGroup> </ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="framework.h" />
<ClInclude Include="GitVer.h" />
<ClInclude Include="GitVer_cli.h" />
<ClInclude Include="GitVer_common.h" />
<ClInclude Include="GitVer_nuitka.h" />
<ClInclude Include="GitVer_process.h" />
<ClInclude Include="GitVer_rewrite.h" />
<ClInclude Include="GitVer_tag.h" />
<ClInclude Include="GitVer_types.h" />
<ClInclude Include="GitVer_version.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="Resource.h" />
<ClInclude Include="targetver.h" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="GitVer.cpp" /> <ClCompile Include="GitVer.cpp" />
<ClCompile Include="GitVer_cli.cpp" />
<ClCompile Include="GitVer_common.cpp" />
<ClCompile Include="GitVer_nuitka.cpp" />
<ClCompile Include="GitVer_process.cpp" />
<ClCompile Include="GitVer_rewrite.cpp" />
<ClCompile Include="GitVer_tag.cpp" />
<ClCompile Include="GitVer_version.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="GitVer.rc" />
</ItemGroup>
<ItemGroup>
<Image Include="app.ico" />
</ItemGroup> </ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets"> <ImportGroup Label="ExtensionTargets">

View File

@@ -14,9 +14,84 @@
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions> <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter> </Filter>
</ItemGroup> </ItemGroup>
<ItemGroup>
<ClInclude Include="framework.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="targetver.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="Resource.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="GitVer.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="pch.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="GitVer_cli.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="GitVer_common.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="GitVer_nuitka.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="GitVer_process.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="GitVer_rewrite.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="GitVer_tag.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="GitVer_types.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="GitVer_version.h">
<Filter>头文件</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="GitVer.cpp"> <ClCompile Include="GitVer.cpp">
<Filter>源文件</Filter> <Filter>源文件</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="pch.cpp">
<Filter>源文件</Filter>
</ClCompile>
<ClCompile Include="GitVer_cli.cpp">
<Filter>源文件</Filter>
</ClCompile>
<ClCompile Include="GitVer_common.cpp">
<Filter>源文件</Filter>
</ClCompile>
<ClCompile Include="GitVer_nuitka.cpp">
<Filter>源文件</Filter>
</ClCompile>
<ClCompile Include="GitVer_process.cpp">
<Filter>源文件</Filter>
</ClCompile>
<ClCompile Include="GitVer_rewrite.cpp">
<Filter>源文件</Filter>
</ClCompile>
<ClCompile Include="GitVer_tag.cpp">
<Filter>源文件</Filter>
</ClCompile>
<ClCompile Include="GitVer_version.cpp">
<Filter>源文件</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="GitVer.rc">
<Filter>资源文件</Filter>
</ResourceCompile>
</ItemGroup>
<ItemGroup>
<Image Include="app.ico">
<Filter>资源文件</Filter>
</Image>
</ItemGroup> </ItemGroup>
</Project> </Project>

89
GitVer/GitVer_cli.cpp Normal file
View File

@@ -0,0 +1,89 @@
#include "pch.h"
#include "gitver.h"
#include "gitver_common.h"
#include "gitver_cli.h"
extern TCHAR g_szCurModuleDir[MAX_PATH];
void PrintInvalidRepoPathError(LPCTSTR lpRepoPath)
{
_tprintf(_T("错误: 无效的仓库路径 %s\n"), lpRepoPath == NULL ? _T("<null>") : lpRepoPath);
}
int ParseSetVerOptions(int argc, TCHAR* argv[], LPCTSTR& lpRepoPath, BOOL& bUseDefaultTagWhenMissing)
{
lpRepoPath = NULL;
bUseDefaultTagWhenMissing = FALSE;
for (int i = 3; i < argc; ++i)
{
CString strArg = argv[i];
if (strArg.CompareNoCase(_T("-f")) == 0)
{
bUseDefaultTagWhenMissing = TRUE;
continue;
}
if (lpRepoPath == NULL)
{
if (!PathIsDirectory(argv[i]))
{
PrintInvalidRepoPathError(argv[i]);
return 23;
}
lpRepoPath = argv[i];
continue;
}
_tprintf(_T("错误: 多余的参数 %s\n"), argv[i]);
return 3;
}
if (lpRepoPath == NULL)
{
lpRepoPath = g_szCurModuleDir;
}
return 0;
}
int ParseRewriteOptions(int argc, TCHAR* argv[], int& nPEType, BOOL& bForceRewrite)
{
nPEType = 1;
bForceRewrite = FALSE;
BOOL bHasPETypeArg = FALSE;
for (int i = 1; i < argc; ++i)
{
CString strArg = argv[i];
if (strArg.CompareNoCase(_T("-f")) == 0)
{
bForceRewrite = TRUE;
continue;
}
UINT nArgValue = 0;
if (!TryParseUInt16(strArg, nArgValue))
{
_tprintf(_T("错误: 无效的 PE 类型 %s\n"), strArg.GetString());
return 22;
}
if (bHasPETypeArg)
{
_tprintf(_T("错误: PE 类型参数重复 %s\n"), strArg.GetString());
return 22;
}
nPEType = (int)nArgValue;
bHasPETypeArg = TRUE;
}
if (nPEType != 1 && nPEType != 2)
{
_tprintf(_T("错误: 无效的 PE 类型 %d\n"), nPEType);
return 22;
}
return 0;
}

5
GitVer/GitVer_cli.h Normal file
View File

@@ -0,0 +1,5 @@
#pragma once
void PrintInvalidRepoPathError(LPCTSTR lpRepoPath);
int ParseSetVerOptions(int argc, TCHAR* argv[], LPCTSTR& lpRepoPath, BOOL& bUseDefaultTagWhenMissing);
int ParseRewriteOptions(int argc, TCHAR* argv[], int& nPEType, BOOL& bForceRewrite);

95
GitVer/GitVer_common.cpp Normal file
View File

@@ -0,0 +1,95 @@
#include "pch.h"
#include "gitver.h"
#include "gitver_common.h"
CString BuildArgsForLog(int argc, TCHAR* argv[])
{
CString strArgs;
for (int i = 0; i < argc; ++i)
{
if (i > 0)
{
strArgs += _T(" ");
}
strArgs += QuoteCmdArg(argv[i]);
}
return strArgs;
}
DWORD LogCommandStart(LPCTSTR lpCommandName)
{
DWORD dwStartTick = GetTickCount();
_tprintf(_T("Command start: %s\n"), lpCommandName);
return dwStartTick;
}
CString QuoteCmdArg(const CString& strArg)
{
if (strArg.Find(_T(' ')) == -1 && strArg.Find(_T('\t')) == -1 && strArg.Find(_T('"')) == -1)
{
return strArg;
}
CString strEscaped = strArg;
strEscaped.Replace(_T("\""), _T("\\\""));
CString strQuoted;
strQuoted.Format(_T("\"%s\""), strEscaped.GetString());
return strQuoted;
}
BOOL TryParseUInt16(const CString& strValue, UINT& nValue)
{
CString strTrimmed = strValue;
strTrimmed.Trim();
if (strTrimmed.IsEmpty())
{
return FALSE;
}
for (int i = 0; i < strTrimmed.GetLength(); ++i)
{
if (!_istdigit(strTrimmed[i]))
{
return FALSE;
}
}
ULONGLONG ullValue = _tcstoui64(strTrimmed, NULL, 10);
if (ullValue > 65535)
{
return FALSE;
}
nValue = (UINT)ullValue;
return TRUE;
}
int ParseUInt16ArgOrError(TCHAR* argv[], int nIndex, LPCTSTR lpArgName, UINT& nValue, int nErrorCode)
{
if (!TryParseUInt16(argv[nIndex], nValue))
{
_tprintf(_T("Error: invalid %s %s\n"), lpArgName, argv[nIndex]);
return nErrorCode;
}
return 0;
}
BOOL IsLikelyPathArg(LPCTSTR lpArg)
{
if (lpArg == NULL)
{
return FALSE;
}
CString strArg = lpArg;
if (strArg.IsEmpty())
{
return FALSE;
}
return strArg.Find(_T('\\')) >= 0
|| strArg.Find(_T('/')) >= 0
|| strArg.Find(_T(':')) >= 0
|| strArg.Left(1) == _T(".");
}

8
GitVer/GitVer_common.h Normal file
View File

@@ -0,0 +1,8 @@
#pragma once
CString BuildArgsForLog(int argc, TCHAR* argv[]);
DWORD LogCommandStart(LPCTSTR lpCommandName);
CString QuoteCmdArg(const CString& strArg);
BOOL TryParseUInt16(const CString& strValue, UINT& nValue);
int ParseUInt16ArgOrError(TCHAR* argv[], int nIndex, LPCTSTR lpArgName, UINT& nValue, int nErrorCode);
BOOL IsLikelyPathArg(LPCTSTR lpArg);

190
GitVer/GitVer_nuitka.cpp Normal file
View File

@@ -0,0 +1,190 @@
#include "pch.h"
#include "gitver.h"
#include "gitver_cli.h"
#include "gitver_common.h"
#include "gitver_process.h"
#include "gitver_version.h"
#include "gitver_nuitka.h"
extern TCHAR g_szCurModuleDir[MAX_PATH];
void PrintCommandUsageAndExamples(LPCTSTR lpCommand, LPCTSTR lpRequiredArgs, LPCTSTR lpEntryExample);
const UINT DEFAULT_MAJOR_WHEN_NO_TAG_FOR_NUITKA = 1;
const UINT DEFAULT_MINOR_WHEN_NO_TAG_FOR_NUITKA = 0;
static int ParseNuitkaOptions(int argc, TCHAR* argv[], LPCTSTR& lpRepoPath, BOOL& bUseDefaultTagWhenMissing, CString& strExtraArgs)
{
lpRepoPath = NULL;
bUseDefaultTagWhenMissing = FALSE;
strExtraArgs.Empty();
BOOL bExtraArgsStarted = FALSE;
for (int i = 4; i < argc; ++i)
{
CString strArg = argv[i];
BOOL bOptionStyleArg = !strArg.IsEmpty() && strArg.Left(1) == _T("-");
// 仅在额外参数尚未开始时,将 -f 解释为 gitver 选项;
// 一旦进入额外参数阶段,-f 应透传给 Nuitka。
if (!bExtraArgsStarted && strArg.CompareNoCase(_T("-f")) == 0)
{
bUseDefaultTagWhenMissing = TRUE;
continue;
}
if (!bExtraArgsStarted && lpRepoPath == NULL && !bOptionStyleArg && PathIsDirectory(argv[i]))
{
lpRepoPath = argv[i];
continue;
}
if (!bExtraArgsStarted && lpRepoPath == NULL && !bOptionStyleArg && IsLikelyPathArg(argv[i]))
{
PrintInvalidRepoPathError(argv[i]);
return 23;
}
bExtraArgsStarted = TRUE;
if (!strExtraArgs.IsEmpty())
{
strExtraArgs += _T(" ");
}
strExtraArgs += QuoteCmdArg(argv[i]);
}
if (lpRepoPath == NULL)
{
lpRepoPath = g_szCurModuleDir;
}
return 0;
}
static void RunNuitkaBuild(LPCTSTR lpStartMessage, CString& strNuitkaCmd, const CString& strProductVersion, const CString& strFileVersion, LPCTSTR lpRepoPath)
{
_tprintf(_T("%s\n"), lpStartMessage);
_tprintf(_T("ProductVersion=%s\n"), strProductVersion.GetString());
_tprintf(_T("FileVersion=%s\n"), strFileVersion.GetString());
CString strBuildResult = StartProcess(NULL, strNuitkaCmd.GetBuffer(), lpRepoPath);
strNuitkaCmd.ReleaseBuffer();
if (strBuildResult.IsEmpty())
{
_tprintf(_T("错误: Nuitka 构建失败。\n"));
}
}
static int HandleNuitkaBuildCommandCore(
int argc,
TCHAR* argv[],
BOOL bBuildModule,
LPCTSTR lpStartMessage,
LPCTSTR lpUsageName,
LPCTSTR lpEntryArgName,
int nArgLackErrorCode,
int nPidErrorCode,
int nBidErrorCode,
int nTagErrorCode,
int nCommitErrorCode)
{
if (argc < 4)
{
_tprintf(_T("错误: 参数不足。\n"));
CString strRequiredArgs;
strRequiredArgs.Format(_T("[pid] [%s]"), lpEntryArgName);
PrintCommandUsageAndExamples(lpUsageName, strRequiredArgs.GetString(), lpEntryArgName);
return nArgLackErrorCode;
}
UINT nPid = 0;
int nArgRet = ParseUInt16ArgOrError(argv, 2, _T("pid"), nPid, nPidErrorCode);
if (nArgRet != 0)
{
return nArgRet;
}
CString strMainOrModulePy = argv[3];
LPCTSTR lpRepoPath = NULL;
BOOL bUseDefaultTagWhenMissing = FALSE;
CString strExtraArgs;
int nRepoArgRet = ParseNuitkaOptions(argc, argv, lpRepoPath, bUseDefaultTagWhenMissing, strExtraArgs);
if (nRepoArgRet != 0)
{
return nRepoArgRet;
}
CString strProductVersion;
CString strFileVersion;
VersionBuildErrorCodes errorCodes = { nBidErrorCode, nTagErrorCode, nCommitErrorCode };
int nVersionRet = BuildVersionsFromRepo(
lpRepoPath,
nPid,
errorCodes,
strProductVersion,
strFileVersion,
bUseDefaultTagWhenMissing,
DEFAULT_MAJOR_WHEN_NO_TAG_FOR_NUITKA,
DEFAULT_MINOR_WHEN_NO_TAG_FOR_NUITKA);
if (nVersionRet != 0)
{
return nVersionRet;
}
CString strNuitkaCmd;
if (bBuildModule)
{
strNuitkaCmd.Format(
_T("cmd /c python -m nuitka --module --windows-product-version=%s --windows-file-version=%s %s %s"),
strProductVersion.GetString(),
strFileVersion.GetString(),
strExtraArgs.GetString(),
QuoteCmdArg(strMainOrModulePy).GetString());
}
else
{
strNuitkaCmd.Format(
_T("cmd /c python -m nuitka --windows-product-version=%s --windows-file-version=%s %s %s"),
strProductVersion.GetString(),
strFileVersion.GetString(),
strExtraArgs.GetString(),
QuoteCmdArg(strMainOrModulePy).GetString());
}
RunNuitkaBuild(lpStartMessage, strNuitkaCmd, strProductVersion, strFileVersion, lpRepoPath);
return 0;
}
int HandleNuitkaBuildCommand(int argc, TCHAR* argv[])
{
return HandleNuitkaBuildCommandCore(
argc,
argv,
FALSE,
_T("Nuitka 打包开始.."),
_T("nuitkabuild"),
_T("mainPy"),
17,
18,
19,
20,
21);
}
int HandleNuitkaPydBuildCommand(int argc, TCHAR* argv[])
{
return HandleNuitkaBuildCommandCore(
argc,
argv,
TRUE,
_T("Nuitka pyd 打包开始.."),
_T("nuitkapydbuild"),
_T("modulePy"),
24,
25,
26,
27,
28);
}

4
GitVer/GitVer_nuitka.h Normal file
View File

@@ -0,0 +1,4 @@
#pragma once
int HandleNuitkaBuildCommand(int argc, TCHAR* argv[]);
int HandleNuitkaPydBuildCommand(int argc, TCHAR* argv[]);

170
GitVer/GitVer_process.cpp Normal file
View File

@@ -0,0 +1,170 @@
#include "pch.h"
#include "gitver.h"
#include "gitver_process.h"
BOOL ReplaceFileContent(LPCTSTR lpFile, std::vector<std::string>& vtOldContent, std::vector<std::string>& vtNewContent)
{
if (vtOldContent.size() != vtNewContent.size())
{
_tprintf(_T("错误: 旧内容和新内容的数量不匹配。\n"));
return FALSE;
}
if (!PathFileExists(lpFile))
{
_tprintf(_T("错误: 文件不存在 %s\n"), lpFile);
return FALSE;
}
CFile myFile;
CFileException fileExp;
if (!myFile.Open(lpFile, CFile::modeReadWrite, &fileExp))
{
_tprintf(_T("错误: 打开文件失败 %s\n"), lpFile);
return FALSE;
}
_tprintf(_T("成功: 打开文件 %s\n"), lpFile);
DWORD dwFileLength = myFile.GetLength();
BYTE* pData = new BYTE[dwFileLength];
memset(pData, 0, dwFileLength);
myFile.Read(pData, dwFileLength);
std::string strContent;
strContent.append((char*)pData, dwFileLength);
delete[]pData;
pData = NULL;
myFile.Close();
for (std::vector<std::string>::iterator it_old = vtOldContent.begin(); it_old != vtOldContent.end(); ++it_old)
{
if (strContent.find(it_old->c_str()) == std::string::npos)
{
_tprintf(_T("错误: 未找到要替换的内容 %s\n"), it_old->c_str());
return FALSE;
}
}
std::vector<std::string>::iterator it_old;
std::vector<std::string>::iterator it_new;
for (it_old = vtOldContent.begin(), it_new = vtNewContent.begin(); it_old != vtOldContent.end() && it_new != vtNewContent.end(); it_old++, it_new++)
{
std::string::size_type nPos = strContent.find(it_old->c_str());
if (nPos != std::string::npos)
{
strContent.replace(nPos, it_old->size(), it_new->c_str());
_tprintf(_T("成功: 替换 %s -> %s\n"), it_old->c_str(), it_new->c_str());
}
}
if (!myFile.Open(lpFile, CFile::modeCreate | CFile::modeWrite, &fileExp))
{
_tprintf(_T("错误: 打开文件失败 %s\n"), lpFile);
return FALSE;
}
myFile.Write(strContent.c_str(), strContent.size());
myFile.Close();
return TRUE;
}
CString StartProcess(LPCTSTR program, LPCTSTR args, LPCTSTR lpCurrentDirectory)
{
CString strValue = _T("");
const int MY_PIPE_BUFFER_SIZE = 8912;
CString strMutableArgs = args == NULL ? _T("") : args;
LPTSTR lpArgs = strMutableArgs.IsEmpty() ? NULL : strMutableArgs.GetBuffer();
HANDLE hPipeRead = NULL;
HANDLE hPipeWrite = NULL;
SECURITY_ATTRIBUTES saOutPipe;
::ZeroMemory(&saOutPipe, sizeof(saOutPipe));
saOutPipe.nLength = sizeof(SECURITY_ATTRIBUTES);
saOutPipe.lpSecurityDescriptor = NULL;
saOutPipe.bInheritHandle = TRUE;
if (CreatePipe(&hPipeRead, &hPipeWrite, &saOutPipe, MY_PIPE_BUFFER_SIZE))
{
PROCESS_INFORMATION processInfo;
::ZeroMemory(&processInfo, sizeof(processInfo));
STARTUPINFO startupInfo;
::ZeroMemory(&startupInfo, sizeof(startupInfo));
startupInfo.cb = sizeof(STARTUPINFO);
startupInfo.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
startupInfo.hStdOutput = hPipeWrite;
startupInfo.hStdError = hPipeWrite;
startupInfo.wShowWindow = SW_HIDE;
BOOL bRet = ::CreateProcess(program,
lpArgs,
NULL,
NULL,
TRUE,
0,
NULL,
lpCurrentDirectory,
&startupInfo,
&processInfo);
if (bRet == 0)
{
int nRet = GetLastError();
_tprintf(_T("错误: 创建进程失败,错误码 %d\n"), nRet);
}
else
{
if (hPipeWrite)
{
CloseHandle(hPipeWrite);
hPipeWrite = NULL;
}
for (;;)
{
char szPipeOut[MY_PIPE_BUFFER_SIZE];
::ZeroMemory(szPipeOut, sizeof(szPipeOut));
DWORD dwStdLen = 0;
if (!ReadFile(hPipeRead, szPipeOut, MY_PIPE_BUFFER_SIZE - 1, &dwStdLen, NULL) || dwStdLen == 0)
{
break;
}
szPipeOut[dwStdLen] = '\0';
#ifdef UNICODE
strValue.AppendFormat(_T("%hs"), szPipeOut);
#else
strValue += szPipeOut;
#endif
}
WaitForSingleObject(processInfo.hProcess, INFINITE);
}
if (processInfo.hProcess)
{
CloseHandle(processInfo.hProcess);
}
if (processInfo.hThread)
{
CloseHandle(processInfo.hThread);
}
}
if (hPipeRead)
{
CloseHandle(hPipeRead);
}
if (hPipeWrite)
{
CloseHandle(hPipeWrite);
}
if (!strMutableArgs.IsEmpty())
{
strMutableArgs.ReleaseBuffer();
}
_tprintf(_T("StartProcess program=%s args=%s cwd=%s output=%s\n"),
program == NULL ? _T("<null>") : program,
args == NULL ? _T("<null>") : args,
lpCurrentDirectory == NULL ? _T("<null>") : lpCurrentDirectory,
strValue.GetString());
return strValue;
}

4
GitVer/GitVer_process.h Normal file
View File

@@ -0,0 +1,4 @@
#pragma once
BOOL ReplaceFileContent(LPCTSTR lpFile, std::vector<std::string>& vtOldContent, std::vector<std::string>& vtNewContent);
CString StartProcess(LPCTSTR program, LPCTSTR args, LPCTSTR lpCurrentDirectory);

442
GitVer/GitVer_rewrite.cpp Normal file
View File

@@ -0,0 +1,442 @@
#include "pch.h"
#include "gitver.h"
#include "gitver_rewrite.h"
#include "gitver_types.h"
extern TCHAR g_szCurModuleDir[MAX_PATH];
std::string ToAnsiString(LPCTSTR lpText);
BOOL FindPreferredCppRcFileAt(LPCTSTR lpBaseDir, CString& strResFile);
BOOL FindPreferredAssemblyInfoFileAt(LPCTSTR lpBaseDir, CString& strResFile);
int DetectProjectCodeType(LPCTSTR lpBaseDir);
LPCTSTR GetProjectCodeTypeName(int nCodeType);
/// <summary>
/// 替换指定范围内包含指定标记的整行内容。
/// </summary>
BOOL ReplaceWholeLineByTokenInRange(std::string& strContent, const std::string& strToken, const std::string& strNewLine, size_t nRangeStart, size_t nRangeEnd)
{
if (nRangeStart == std::string::npos || nRangeEnd == std::string::npos || nRangeStart >= nRangeEnd)
{
_tprintf(_T("错误: 无效的范围参数。\n"));
return FALSE;
}
std::string::size_type nPos = strContent.find(strToken, nRangeStart);
if (nPos == std::string::npos || nPos >= nRangeEnd)
{
_tprintf(_T("错误: 未找到指定的标记 %s。\n"), strToken.c_str());
return FALSE;
}
std::string::size_type nLineStart = strContent.rfind('\n', nPos);
if (nLineStart == std::string::npos)
{
nLineStart = 0;
}
else
{
++nLineStart;
}
std::string::size_type nLineEnd = strContent.find('\n', nPos);
if (nLineEnd == std::string::npos)
{
nLineEnd = strContent.size();
}
if (nLineEnd > nLineStart && strContent[nLineEnd - 1] == '\r')
{
--nLineEnd;
}
strContent.replace(nLineStart, nLineEnd - nLineStart, strNewLine);
return TRUE;
}
BOOL ReplaceWholeLineByToken(std::string& strContent, const std::string& strToken, const std::string& strNewLine)
{
return ReplaceWholeLineByTokenInRange(strContent, strToken, strNewLine, 0, strContent.size());
}
BOOL EnsureSingleLineByToken(std::string& strContent, const std::string& strToken, const std::string& strNewLine)
{
std::string::size_type nPos = strContent.find(strToken);
if (nPos == std::string::npos)
{
_tprintf(_T("错误: 未找到指定的标记 %s。\n"), strToken.c_str());
return FALSE;
}
std::string::size_type nLineStart = strContent.rfind('\n', nPos);
if (nLineStart == std::string::npos)
{
nLineStart = 0;
}
else
{
++nLineStart;
}
std::string::size_type nLineEnd = strContent.find('\n', nPos);
if (nLineEnd == std::string::npos)
{
nLineEnd = strContent.size();
}
if (nLineEnd > nLineStart && strContent[nLineEnd - 1] == '\r')
{
--nLineEnd;
}
strContent.replace(nLineStart, nLineEnd - nLineStart, strNewLine);
std::string::size_type nSearchPos = nLineStart + strNewLine.size();
for (;;)
{
std::string::size_type nDupPos = strContent.find(strToken, nSearchPos);
if (nDupPos == std::string::npos)
{
break;
}
std::string::size_type nDupLineStart = strContent.rfind('\n', nDupPos);
if (nDupLineStart == std::string::npos)
{
nDupLineStart = 0;
}
else
{
++nDupLineStart;
}
std::string::size_type nDupLineEnd = strContent.find('\n', nDupPos);
if (nDupLineEnd == std::string::npos)
{
nDupLineEnd = strContent.size();
}
else
{
++nDupLineEnd;
}
strContent.erase(nDupLineStart, nDupLineEnd - nDupLineStart);
nSearchPos = nDupLineStart;
}
return TRUE;
}
BOOL EnsureSingleLineByTokenOrAppend(std::string& strContent, const std::string& strToken, const std::string& strNewLine)
{
if (strContent.find(strToken) != std::string::npos)
{
return EnsureSingleLineByToken(strContent, strToken, strNewLine);
}
if (!strContent.empty())
{
if (strContent.rfind("\r\n") != strContent.size() - 2 && strContent[strContent.size() - 1] != '\n')
{
strContent += "\r\n";
}
}
strContent += strNewLine;
strContent += "\r\n";
return TRUE;
}
BOOL FindVersionInfoBlockRange(const std::string& strContent, size_t& nBlockStart, size_t& nBlockEnd)
{
nBlockStart = std::string::npos;
nBlockEnd = std::string::npos;
std::string::size_type nVersionPos = strContent.find("VS_VERSION_INFO VERSIONINFO");
if (nVersionPos == std::string::npos)
{
return FALSE;
}
std::string::size_type nStart = strContent.rfind('\n', nVersionPos);
nBlockStart = (nStart == std::string::npos) ? 0 : (nStart + 1);
BOOL bSeenBegin = FALSE;
int nDepth = 0;
std::string::size_type nLineStart = nBlockStart;
while (nLineStart < strContent.size())
{
std::string::size_type nNextLine = strContent.find('\n', nLineStart);
std::string::size_type nLineEnd = (nNextLine == std::string::npos) ? strContent.size() : nNextLine;
std::string strLine = strContent.substr(nLineStart, nLineEnd - nLineStart);
if (!strLine.empty() && strLine[strLine.size() - 1] == '\r')
{
strLine.erase(strLine.size() - 1);
}
if (!bSeenBegin)
{
if (strLine.find("BEGIN") != std::string::npos)
{
bSeenBegin = TRUE;
nDepth = 1;
}
}
else
{
if (strLine.find("BEGIN") != std::string::npos)
{
++nDepth;
}
if (strLine.find("END") != std::string::npos)
{
--nDepth;
if (nDepth == 0)
{
nBlockEnd = (nNextLine == std::string::npos) ? strContent.size() : (nNextLine + 1);
return TRUE;
}
}
}
if (nNextLine == std::string::npos)
{
break;
}
nLineStart = nNextLine + 1;
}
return FALSE;
}
BOOL DetectSupportedTextBom(const std::string& strData, size_t& nBomLength)
{
nBomLength = 0;
if (strData.size() >= 3
&& (unsigned char)strData[0] == 0xEF
&& (unsigned char)strData[1] == 0xBB
&& (unsigned char)strData[2] == 0xBF)
{
nBomLength = 3;
return TRUE;
}
if (strData.size() >= 2)
{
unsigned char b0 = (unsigned char)strData[0];
unsigned char b1 = (unsigned char)strData[1];
if ((b0 == 0xFF && b1 == 0xFE) || (b0 == 0xFE && b1 == 0xFF))
{
_tprintf(_T("错误: 不支持 UTF-16 编码的文本文件。\n"));
return FALSE;
}
}
return TRUE;
}
BOOL ReadTextFileAsAnsi(LPCTSTR lpFile, std::string& strContent, std::string& strBomPrefix)
{
strContent.clear();
strBomPrefix.clear();
if (!PathFileExists(lpFile))
{
_tprintf(_T("错误: 文件不存在 %s\n"), lpFile);
return FALSE;
}
CFile myFile;
CFileException fileExp;
if (!myFile.Open(lpFile, CFile::modeRead, &fileExp))
{
_tprintf(_T("错误: 打开文件失败 %s\n"), lpFile);
return FALSE;
}
DWORD dwFileLength = myFile.GetLength();
BYTE* pData = new BYTE[dwFileLength];
memset(pData, 0, dwFileLength);
myFile.Read(pData, dwFileLength);
myFile.Close();
std::string strRaw((char*)pData, dwFileLength);
delete[] pData;
pData = NULL;
size_t nBomLength = 0;
if (!DetectSupportedTextBom(strRaw, nBomLength))
{
_tprintf(_T("错误: 检测不支持的文本 BOM。\n"));
return FALSE;
}
if (nBomLength > 0)
{
strBomPrefix = strRaw.substr(0, nBomLength);
strContent = strRaw.substr(nBomLength);
}
else
{
strContent = strRaw;
}
return TRUE;
}
BOOL WriteTextFileAsAnsi(LPCTSTR lpFile, const std::string& strContent, const std::string& strBomPrefix)
{
CFile myFile;
CFileException fileExp;
if (!myFile.Open(lpFile, CFile::modeCreate | CFile::modeWrite, &fileExp))
{
_tprintf(_T("错误: 打开文件失败 %s\n"), lpFile);
return FALSE;
}
if (!strBomPrefix.empty())
{
myFile.Write(strBomPrefix.c_str(), strBomPrefix.size());
}
myFile.Write(strContent.c_str(), strContent.size());
myFile.Close();
return TRUE;
}
BOOL RewriteSetVerToCppRc(const CString& strRcFile, const CString& strProductVersion, const CString& strFileVersion)
{
std::string strContent;
std::string strBomPrefix;
if (!ReadTextFileAsAnsi(strRcFile, strContent, strBomPrefix))
{
_tprintf(_T("错误: 读取 .rc 文件失败 %s\n"), strRcFile.GetString());
return FALSE;
}
CString strFileVersionComma = strFileVersion;
CString strProductVersionComma = strProductVersion;
strFileVersionComma.Replace(_T('.'), _T(','));
strProductVersionComma.Replace(_T('.'), _T(','));
std::string strLineFileVersion = ToAnsiString(CString(_T(" FILEVERSION ")) + strFileVersionComma);
std::string strLineProductVersion = ToAnsiString(CString(_T(" PRODUCTVERSION ")) + strProductVersionComma);
CString strFileValueLine;
strFileValueLine.Format(_T(" VALUE \"FileVersion\", \"%s\""), strFileVersion.GetString());
CString strProductValueLine;
strProductValueLine.Format(_T(" VALUE \"ProductVersion\", \"%s\""), strProductVersion.GetString());
size_t nVersionBlockStart = std::string::npos;
size_t nVersionBlockEnd = std::string::npos;
if (!FindVersionInfoBlockRange(strContent, nVersionBlockStart, nVersionBlockEnd))
{
_tprintf(_T("错误: 找不到版本信息块。\n"));
return FALSE;
}
BOOL bOk = TRUE;
bOk = ReplaceWholeLineByTokenInRange(strContent, "FILEVERSION", strLineFileVersion, nVersionBlockStart, nVersionBlockEnd) && bOk;
bOk = ReplaceWholeLineByTokenInRange(strContent, "PRODUCTVERSION", strLineProductVersion, nVersionBlockStart, nVersionBlockEnd) && bOk;
bOk = ReplaceWholeLineByTokenInRange(strContent, "VALUE \"FileVersion\",", ToAnsiString(strFileValueLine), nVersionBlockStart, nVersionBlockEnd) && bOk;
bOk = ReplaceWholeLineByTokenInRange(strContent, "VALUE \"ProductVersion\",", ToAnsiString(strProductValueLine), nVersionBlockStart, nVersionBlockEnd) && bOk;
if (!bOk)
{
_tprintf(_T("错误: 重写 .rc 文件版本信息失败。\n"));
return FALSE;
}
if (!WriteTextFileAsAnsi(strRcFile, strContent, strBomPrefix))
{
_tprintf(_T("错误: 写入 .rc 文件失败 %s\n"), strRcFile.GetString());
return FALSE;
}
_tprintf(_T("成功: 重写 .rc 文件版本信息 %s\n"), strRcFile.GetString());
return TRUE;
}
BOOL RewriteSetVerToAssemblyInfo(const CString& strAssemblyInfoFile, const CString& strProductVersion, const CString& strFileVersion)
{
std::string strContent;
std::string strBomPrefix;
if (!ReadTextFileAsAnsi(strAssemblyInfoFile, strContent, strBomPrefix))
{
return FALSE;
}
CString strAssemblyVersionLine;
strAssemblyVersionLine.Format(_T("[assembly: AssemblyVersion(\"%s\")]"), strProductVersion.GetString());
CString strAssemblyFileVersionLine;
strAssemblyFileVersionLine.Format(_T("[assembly: AssemblyFileVersion(\"%s\")]"), strFileVersion.GetString());
CString strAssemblyInformationalVersionLine;
strAssemblyInformationalVersionLine.Format(_T("[assembly: AssemblyInformationalVersion(\"%s\")]"), strProductVersion.GetString());
BOOL bOk = TRUE;
bOk = EnsureSingleLineByToken(strContent, "[assembly: AssemblyVersion(\"", ToAnsiString(strAssemblyVersionLine)) && bOk;
bOk = EnsureSingleLineByToken(strContent, "[assembly: AssemblyFileVersion(\"", ToAnsiString(strAssemblyFileVersionLine)) && bOk;
bOk = EnsureSingleLineByTokenOrAppend(strContent, "[assembly: AssemblyInformationalVersion(\"", ToAnsiString(strAssemblyInformationalVersionLine)) && bOk;
if (!bOk)
{
_tprintf(_T("错误: 重写 AssemblyInfo 文件版本信息失败。\n"));
return FALSE;
}
if (!WriteTextFileAsAnsi(strAssemblyInfoFile, strContent, strBomPrefix))
{
return FALSE;
}
_tprintf(_T("成功: 重写 AssemblyInfo 文件版本信息 %s\n"), strAssemblyInfoFile.GetString());
return TRUE;
}
int RewriteSetVerByProjectType(LPCTSTR lpRepoPath, const CString& strProductVersion, const CString& strFileVersion)
{
LPCTSTR lpBaseDir = lpRepoPath == NULL ? g_szCurModuleDir : lpRepoPath;
int nCodeType = DetectProjectCodeType(lpBaseDir);
_tprintf(_T("重写版本信息,项目路径: %s项目类型: %s\n"),
lpBaseDir == NULL ? _T("<null>") : lpBaseDir,
GetProjectCodeTypeName(nCodeType));
if (nCodeType == PROJECT_CPP)
{
CString strRcFile;
if (!FindPreferredCppRcFileAt(lpBaseDir, strRcFile))
{
_tprintf(_T("错误: 找不到首选的 .rc 文件。\n"));
return 35;
}
if (!RewriteSetVerToCppRc(strRcFile, strProductVersion, strFileVersion))
{
_tprintf(_T("错误: 重写 .rc 文件版本信息失败。\n"));
return 35;
}
return 0;
}
if (nCodeType == PROJECT_CSHARP)
{
CString strAssemblyInfoFile;
if (!FindPreferredAssemblyInfoFileAt(lpBaseDir, strAssemblyInfoFile))
{
_tprintf(_T("错误: 找不到首选的 AssemblyInfo 文件。\n"));
return 35;
}
if (!RewriteSetVerToAssemblyInfo(strAssemblyInfoFile, strProductVersion, strFileVersion))
{
_tprintf(_T("错误: 重写 AssemblyInfo 文件版本信息失败。\n"));
return 35;
}
return 0;
}
if (nCodeType == PROJECT_PYTHON)
{
_tprintf(_T("错误: 不支持的项目类型: Python。\n"));
return 15;
}
_tprintf(_T("错误: 不支持的项目类型。\n"));
return 15;
}

3
GitVer/GitVer_rewrite.h Normal file
View File

@@ -0,0 +1,3 @@
#pragma once
int RewriteSetVerByProjectType(LPCTSTR lpRepoPath, const CString& strProductVersion, const CString& strFileVersion);

235
GitVer/GitVer_tag.cpp Normal file
View File

@@ -0,0 +1,235 @@
#include "pch.h"
#include "gitver.h"
#include "gitver_common.h"
#include "gitver_process.h"
#include "gitver_version.h"
#include "gitver_tag.h"
extern TCHAR g_szCurModuleDir[MAX_PATH];
const UINT DEFAULT_MAJOR_WHEN_NO_TAG_FOR_TAG = 1;
const UINT DEFAULT_MINOR_WHEN_NO_TAG_FOR_TAG = 0;
BOOL GetRecentMajorMinorTags(LPCTSTR lpRepoPath, CString& strBranch, std::vector<BranchTagInfo>& vtTags, int nMaxCount)
{
vtTags.clear();
if (nMaxCount <= 0)
{
return FALSE;
}
strBranch.Empty();
if (!TryGetCurrentBranch(lpRepoPath, strBranch))
{
return FALSE;
}
_tprintf(_T("获取最近标签列表:\n"));
CString strTagList = StartProcess(NULL, _T("cmd /c git tag --list --sort=v:refname"), lpRepoPath);
if (strTagList.IsEmpty())
{
return TRUE;
}
std::vector<BranchTagInfo> vtAllTags;
int nCurPos = 0;
CString strLine = strTagList.Tokenize(_T("\r\n"), nCurPos);
while (!strLine.IsEmpty())
{
strLine.Trim();
if (strLine.Left(6).CompareNoCase(_T("fatal:")) == 0)
{
return FALSE;
}
BranchTagInfo tagInfo;
tagInfo.nMajor = 0;
tagInfo.nMinor = 0;
if (TryParseTagMajorMinor(strBranch, strLine, tagInfo.nMajor, tagInfo.nMinor))
{
tagInfo.strTag = strLine;
vtAllTags.push_back(tagInfo);
}
strLine = strTagList.Tokenize(_T("\r\n"), nCurPos);
}
if (vtAllTags.empty())
{
return TRUE;
}
int nStart = (int)vtAllTags.size() - nMaxCount;
if (nStart < 0)
{
nStart = 0;
}
for (int i = nStart; i < (int)vtAllTags.size(); ++i)
{
vtTags.push_back(vtAllTags[i]);
}
return TRUE;
}
int HandleCreateTagInteractive()
{
CString strBranch;
LPCTSTR lpRepoPath = g_szCurModuleDir;
_tprintf(_T("标签创建流程启动,仓库路径: %s\n"), lpRepoPath == NULL ? _T("<null>") : lpRepoPath);
std::vector<BranchTagInfo> vtTags;
if (!GetRecentMajorMinorTags(lpRepoPath, strBranch, vtTags, 3))
{
_tprintf(_T("错误: 获取最近标签失败\n"));
return 30;
}
UINT nNewMajor = 0;
UINT nNewMinor = 0;
if (vtTags.empty())
{
_tprintf(_T("当前分支 %s\n"), strBranch.GetString());
_tprintf(_T("未找到符合 <当前分支>.major.minor 格式的历史标签。\n"));
_tprintf(_T("请设置首个标签的默认 major/minor直接回车将使用默认值\n"));
UINT nInputMajor = DEFAULT_MAJOR_WHEN_NO_TAG_FOR_TAG;
UINT nInputMinor = DEFAULT_MINOR_WHEN_NO_TAG_FOR_TAG;
TCHAR szMajorInput[32] = { 0 };
_tprintf(_T("请输入 major默认 %u\n"), DEFAULT_MAJOR_WHEN_NO_TAG_FOR_TAG);
if (_fgetts(szMajorInput, sizeof(szMajorInput) / sizeof(TCHAR), stdin) == NULL)
{
_tprintf(_T("错误: 读取输入失败\n"));
return 31;
}
CString strMajorInput = szMajorInput;
strMajorInput.Trim();
if (!strMajorInput.IsEmpty() && !TryParseUInt16(strMajorInput, nInputMajor))
{
_tprintf(_T("错误: 无效的 major %s\n"), strMajorInput.GetString());
return 32;
}
TCHAR szMinorInput[32] = { 0 };
_tprintf(_T("请输入 minor默认 %u\n"), DEFAULT_MINOR_WHEN_NO_TAG_FOR_TAG);
if (_fgetts(szMinorInput, sizeof(szMinorInput) / sizeof(TCHAR), stdin) == NULL)
{
_tprintf(_T("错误: 读取输入失败\n"));
return 31;
}
CString strMinorInput = szMinorInput;
strMinorInput.Trim();
if (!strMinorInput.IsEmpty() && !TryParseUInt16(strMinorInput, nInputMinor))
{
_tprintf(_T("错误: 无效的 minor %s\n"), strMinorInput.GetString());
return 32;
}
nNewMajor = nInputMajor;
nNewMinor = nInputMinor;
_tprintf(_T("将使用起始版本: %s.%u.%u\n"), strBranch.GetString(), nNewMajor, nNewMinor);
}
else
{
const BranchTagInfo& lastTag = vtTags[vtTags.size() - 1];
_tprintf(_T("当前分支 %s\n"), strBranch.GetString());
_tprintf(_T("最近标签数量 %u\n"), (UINT)vtTags.size());
for (size_t i = 0; i < vtTags.size(); ++i)
{
_tprintf(_T(" %u. %s\n"), (UINT)(i + 1), vtTags[i].strTag.GetString());
}
_tprintf(_T("最近标签 %s\n"), lastTag.strTag.GetString());
_tprintf(_T("请选择操作:\n"));
_tprintf(_T(" 1. 增加主版本号\n"));
_tprintf(_T(" 2. 增加次版本号\n"));
_tprintf(_T("请输入 1 或 2\n"));
TCHAR szInput[32] = { 0 };
if (_fgetts(szInput, sizeof(szInput) / sizeof(TCHAR), stdin) == NULL)
{
_tprintf(_T("错误: 读取输入失败\n"));
return 31;
}
CString strChoice = szInput;
strChoice.Trim();
nNewMajor = lastTag.nMajor;
nNewMinor = lastTag.nMinor;
if (strChoice == _T("1"))
{
if (nNewMajor >= 65534)
{
_tprintf(_T("错误: 主版本号超过最大值 %u\n"), nNewMajor);
return 32;
}
++nNewMajor;
nNewMinor = 0;
}
else if (strChoice == _T("2"))
{
if (nNewMinor >= 65534)
{
_tprintf(_T("错误: 次版本号超过最大值 %u\n"), nNewMinor);
return 32;
}
++nNewMinor;
}
else
{
_tprintf(_T("错误: 无效的选择 %s\n"), strChoice.GetString());
return 32;
}
}
CString strNewTag;
strNewTag.Format(_T("%s.%u.%u"), strBranch.GetString(), nNewMajor, nNewMinor);
_tprintf(_T("待创建标签: %s\n"), strNewTag.GetString());
_tprintf(_T("确认创建请输入 y 或 yes其它任意输入将取消\n"));
TCHAR szConfirm[32] = { 0 };
if (_fgetts(szConfirm, sizeof(szConfirm) / sizeof(TCHAR), stdin) == NULL)
{
_tprintf(_T("错误: 读取输入失败\n"));
return 31;
}
CString strConfirm = szConfirm;
strConfirm.Trim();
if (strConfirm.CompareNoCase(_T("y")) != 0 && strConfirm.CompareNoCase(_T("yes")) != 0)
{
_tprintf(_T("错误: 未确认创建标签 %s\n"), strNewTag.GetString());
return 0;
}
CString strCreateCmd;
strCreateCmd.Format(_T("cmd /c git tag %s"), QuoteCmdArg(strNewTag).GetString());
CString strCreateResult = StartProcess(NULL, strCreateCmd.GetBuffer(), lpRepoPath);
strCreateCmd.ReleaseBuffer();
strCreateResult.Trim();
if (strCreateResult.Left(6).CompareNoCase(_T("fatal:")) == 0 || strCreateResult.Left(6).CompareNoCase(_T("error:")) == 0)
{
_tprintf(_T("错误: 创建标签失败 %s\n"), strCreateResult.GetString());
return 33;
}
CString strVerifyCmd;
strVerifyCmd.Format(_T("cmd /c git tag --list %s"), QuoteCmdArg(strNewTag).GetString());
CString strVerifyResult = StartProcess(NULL, strVerifyCmd.GetBuffer(), lpRepoPath);
strVerifyCmd.ReleaseBuffer();
strVerifyResult.Replace(_T("\r"), _T(""));
strVerifyResult.Replace(_T("\n"), _T(""));
strVerifyResult.Trim();
if (strVerifyResult.CompareNoCase(strNewTag) != 0)
{
_tprintf(_T("错误: 验证标签 %s 失败\n"), strNewTag.GetString());
return 34;
}
_tprintf(_T("成功: 创建标签 %s\n"), strNewTag.GetString());
return 0;
}

11
GitVer/GitVer_tag.h Normal file
View File

@@ -0,0 +1,11 @@
#pragma once
struct BranchTagInfo
{
CString strTag;
UINT nMajor;
UINT nMinor;
};
BOOL GetRecentMajorMinorTags(LPCTSTR lpRepoPath, CString& strBranch, std::vector<BranchTagInfo>& vtTags, int nMaxCount);
int HandleCreateTagInteractive();

9
GitVer/GitVer_types.h Normal file
View File

@@ -0,0 +1,9 @@
#pragma once
enum ProjectCodeType
{
PROJECT_UNKNOWN = 0,
PROJECT_CPP = 1,
PROJECT_CSHARP = 2,
PROJECT_PYTHON = 3,
};

244
GitVer/GitVer_version.cpp Normal file
View File

@@ -0,0 +1,244 @@
#include "pch.h"
#include "gitver.h"
#include "gitver_common.h"
#include "gitver_process.h"
#include "gitver_version.h"
BOOL TryGetCurrentBranch(LPCTSTR lpRepoPath, CString& strBranch)
{
_tprintf(_T("获取当前分支名称:\n"));
strBranch = StartProcess(NULL, _T("cmd /c git rev-parse --abbrev-ref HEAD"), lpRepoPath);
strBranch.Replace(_T("\r"), _T(""));
strBranch.Replace(_T("\n"), _T(""));
strBranch.Trim();
if (strBranch.IsEmpty() || strBranch.Left(6).CompareNoCase(_T("fatal:")) == 0)
{
return FALSE;
}
return TRUE;
}
BOOL TryGetBidFromBranch(const CString& strBranch, UINT& nBid)
{
nBid = 0;
if (strBranch.IsEmpty())
{
return FALSE;
}
if (strBranch.CompareNoCase(_T("main")) == 0 || strBranch.CompareNoCase(_T("master")) == 0)
{
nBid = 0;
return TRUE;
}
int nDotPos = strBranch.ReverseFind(_T('.'));
if (nDotPos <= 0 || nDotPos >= strBranch.GetLength() - 1)
{
return FALSE;
}
CString strNum = strBranch.Mid(nDotPos + 1);
return TryParseUInt16(strNum, nBid);
}
BOOL TryParseTagMajorMinor(const CString& strBranch, const CString& strTag, UINT& nMajor, UINT& nMinor)
{
if (strBranch.IsEmpty())
{
return FALSE;
}
CString strPrefix = strBranch + _T(".");
if (strTag.GetLength() <= strPrefix.GetLength())
{
return FALSE;
}
if (strTag.Left(strPrefix.GetLength()).CompareNoCase(strPrefix) != 0)
{
return FALSE;
}
CString strVersion = strTag.Mid(strPrefix.GetLength());
int nDotPos = strVersion.Find(_T('.'));
if (nDotPos <= 0 || nDotPos != strVersion.ReverseFind(_T('.')))
{
return FALSE;
}
CString strMajor = strVersion.Left(nDotPos);
CString strMinor = strVersion.Mid(nDotPos + 1);
if (!TryParseUInt16(strMajor, nMajor) || !TryParseUInt16(strMinor, nMinor))
{
return FALSE;
}
return TRUE;
}
BOOL GetLastMajorMinorTag(LPCTSTR lpRepoPath, const CString& strBranch, CString& strTag, UINT& nMajor, UINT& nMinor)
{
strTag.Empty();
nMajor = 0;
nMinor = 0;
if (strBranch.IsEmpty())
{
return FALSE;
}
CString strTagList = StartProcess(NULL, _T("cmd /c git tag --list --sort=v:refname"), lpRepoPath);
if (strTagList.IsEmpty())
{
_tprintf(_T("错误: 获取标签列表失败\n"));
return FALSE;
}
int nCurPos = 0;
CString strLine = strTagList.Tokenize(_T("\r\n"), nCurPos);
while (!strLine.IsEmpty())
{
strLine.Trim();
if (strLine.Left(6).CompareNoCase(_T("fatal:")) == 0)
{
_tprintf(_T("错误: 获取标签列表失败 %s\n"), strLine.GetString());
return FALSE;
}
UINT nTmpMajor = 0;
UINT nTmpMinor = 0;
if (TryParseTagMajorMinor(strBranch, strLine, nTmpMajor, nTmpMinor))
{
strTag = strLine;
nMajor = nTmpMajor;
nMinor = nTmpMinor;
}
strLine = strTagList.Tokenize(_T("\r\n"), nCurPos);
}
return !strTag.IsEmpty();
}
void GetTodayFileVersionDate(UINT& nYY, UINT& nMMDD)
{
SYSTEMTIME stLocal = { 0 };
::GetLocalTime(&stLocal);
nYY = stLocal.wYear % 100;
nMMDD = (stLocal.wMonth + 10) * 100 + stLocal.wDay;
}
BOOL GetTodayBranchCommitCount(LPCTSTR lpRepoPath, const CString& strBranch, UINT& nId)
{
nId = 0;
if (strBranch.IsEmpty())
{
return FALSE;
}
SYSTEMTIME stLocal = { 0 };
::GetLocalTime(&stLocal);
CString strDate;
strDate.Format(_T("%04u-%02u-%02u"), stLocal.wYear, stLocal.wMonth, stLocal.wDay);
CString strCmd;
strCmd.Format(
_T("cmd /c git rev-list --count --since=\"%s 00:00:00\" --until=\"%s 23:59:59\" \"%s\""),
strDate.GetString(),
strDate.GetString(),
strBranch.GetString());
CString strCount = StartProcess(NULL, strCmd.GetBuffer(), lpRepoPath);
strCmd.ReleaseBuffer();
if (strCount.IsEmpty())
{
return FALSE;
}
strCount.Replace(_T("\r"), _T(""));
strCount.Replace(_T("\n"), _T(""));
strCount.Trim();
if (strCount.IsEmpty() || strCount.Left(6).CompareNoCase(_T("fatal:")) == 0)
{
return FALSE;
}
return TryParseUInt16(strCount, nId);
}
int BuildVersionsFromRepo(
LPCTSTR lpRepoPath,
UINT nPid,
const VersionBuildErrorCodes& errorCodes,
CString& strProductVersion,
CString& strFileVersion,
BOOL bUseDefaultTagWhenMissing,
UINT nDefaultMajor,
UINT nDefaultMinor)
{
_tprintf(_T("开始构建版本: 仓库路径=%s pid=%u 默认Tag(%s)=%u.%u\n"),
lpRepoPath == NULL ? _T("<null>") : lpRepoPath,
nPid,
bUseDefaultTagWhenMissing ? _T("启用") : _T("关闭"),
nDefaultMajor,
nDefaultMinor);
UINT nBid = 0;
CString strBranch;
if (!TryGetCurrentBranch(lpRepoPath, strBranch))
{
_tprintf(_T("错误: 获取当前分支失败。\n"));
return errorCodes.nBidErrorCode;
}
_tprintf(_T("当前分支: %s\n"), strBranch.GetString());
if (!TryGetBidFromBranch(strBranch, nBid))
{
_tprintf(_T("错误: 从分支获取 bid 失败。\n"));
return errorCodes.nBidErrorCode;
}
_tprintf(_T("分支 bid: %u\n"), nBid);
CString strLastTag;
UINT nMajor = 0;
UINT nMinor = 0;
if (!GetLastMajorMinorTag(lpRepoPath, strBranch, strLastTag, nMajor, nMinor))
{
if (!bUseDefaultTagWhenMissing)
{
_tprintf(_T("错误: 获取标签失败。\n"));
return errorCodes.nTagErrorCode;
}
nMajor = nDefaultMajor;
nMinor = nDefaultMinor;
_tprintf(_T("使用默认标签 %u %u\n"), nMajor, nMinor);
}
else
{
_tprintf(_T("命中最新标签: %s (major=%u minor=%u)\n"), strLastTag.GetString(), nMajor, nMinor);
}
UINT nYY = 0;
UINT nMMDD = 0;
GetTodayFileVersionDate(nYY, nMMDD);
UINT nId = 0;
if (!GetTodayBranchCommitCount(lpRepoPath, strBranch, nId))
{
_tprintf(_T("错误: 获取今日分支提交计数失败。\n"));
return errorCodes.nCommitErrorCode;
}
_tprintf(_T("今日提交计数: %u\n"), nId);
strProductVersion.Format(_T("%u.%u.%u.%u"), nPid, nBid, nMajor, nMinor);
strFileVersion.Format(_T("%u.%u.%u.%u"), nPid, nYY, nMMDD, nId);
_tprintf(_T("版本构建完成: ProductVersion=%s FileVersion=%s\n"), strProductVersion.GetString(), strFileVersion.GetString());
return 0;
}

24
GitVer/GitVer_version.h Normal file
View File

@@ -0,0 +1,24 @@
#pragma once
struct VersionBuildErrorCodes
{
int nBidErrorCode;
int nTagErrorCode;
int nCommitErrorCode;
};
BOOL TryGetCurrentBranch(LPCTSTR lpRepoPath, CString& strBranch);
BOOL TryGetBidFromBranch(const CString& strBranch, UINT& nBid);
BOOL TryParseTagMajorMinor(const CString& strBranch, const CString& strTag, UINT& nMajor, UINT& nMinor);
BOOL GetLastMajorMinorTag(LPCTSTR lpRepoPath, const CString& strBranch, CString& strTag, UINT& nMajor, UINT& nMinor);
void GetTodayFileVersionDate(UINT& nYY, UINT& nMMDD);
BOOL GetTodayBranchCommitCount(LPCTSTR lpRepoPath, const CString& strBranch, UINT& nId);
int BuildVersionsFromRepo(
LPCTSTR lpRepoPath,
UINT nPid,
const VersionBuildErrorCodes& errorCodes,
CString& strProductVersion,
CString& strFileVersion,
BOOL bUseDefaultTagWhenMissing,
UINT nDefaultMajor,
UINT nDefaultMinor);

BIN
GitVer/app.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

26
GitVer/framework.h Normal file
View File

@@ -0,0 +1,26 @@
#pragma once
#include "targetver.h"
#include <stdio.h>
#include <tchar.h>
#define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS // 部分 CString 构造函数将是显式的
#define _AFX_NO_MFC_CONTROLS_IN_DIALOGS // 移除对话框中的 MFC 控件支持
#ifndef VC_EXTRALEAN
#define VC_EXTRALEAN // 从 Windows 头文件中排除极少使用的内容
#endif
#include <afx.h>
#include <afxwin.h> // MFC 核心组件和标准组件
#include <afxext.h> // MFC 扩展
#ifndef _AFX_NO_OLE_SUPPORT
#include <afxdtctl.h> // MFC 对 Internet Explorer 4 公共控件的支持
#endif
#ifndef _AFX_NO_AFXCMN_SUPPORT
#include <afxcmn.h> // MFC 对 Windows 公共控件的支持
#endif // _AFX_NO_AFXCMN_SUPPORT
#include <iostream>
#include <vector>
#include <string>
#include <Shlwapi.h>

5
GitVer/pch.cpp Normal file
View File

@@ -0,0 +1,5 @@
// pch.cpp: 与预编译标头对应的源文件
#include "pch.h"
// 当使用预编译的头时,需要使用此源文件,编译才能成功。

13
GitVer/pch.h Normal file
View File

@@ -0,0 +1,13 @@
// pch.h: 这是预编译标头文件。
// 下方列出的文件仅编译一次,提高了将来生成的生成性能。
// 这还将影响 IntelliSense 性能,包括代码完成和许多代码浏览功能。
// 但是,如果此处列出的文件中的任何一个在生成之间有更新,它们全部都将被重新编译。
// 请勿在此处添加要频繁更新的文件,这将使得性能优势无效。
#ifndef PCH_H
#define PCH_H
// 添加要在此处预编译的标头
#include "framework.h"
#endif //PCH_H

18
GitVer/resource.h Normal file
View File

@@ -0,0 +1,18 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ 生成的包含文件。
// 供 GitVer.rc 使用
//
#define IDI_ICON_APP 100
#define IDI_ICON1 101
#define IDS_APP_TITLE 103
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 102
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1000
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif

6
GitVer/targetver.h Normal file
View File

@@ -0,0 +1,6 @@
#pragma once
// // 包含 SDKDDKVer.h 可定义可用的最高版本的 Windows 平台。
// 如果希望为之前的 Windows 平台构建应用程序,在包含 SDKDDKVer.h 之前请先包含 WinSDKVer.h 并
// 将 _WIN32_WINNT 宏设置为想要支持的平台。
#include <SDKDDKVer.h>