From 89f53efb3306db2915bebe47504721d67def7bfb Mon Sep 17 00:00:00 2001 From: Jeff Date: Thu, 23 Apr 2026 14:55:44 +0800 Subject: [PATCH] =?UTF-8?q?1=E3=80=81=E5=8A=A0=E5=85=A5=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=202=E3=80=81=E9=85=8D=E7=BD=AE=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- GitVer.sln | 20 +- GitVer/GitVer.cpp | 876 +++++++++++++++++++++++++++++++++- GitVer/GitVer.h | 3 + GitVer/GitVer.rc | Bin 0 -> 5406 bytes GitVer/GitVer.vcxproj | 61 ++- GitVer/GitVer.vcxproj.filters | 75 +++ GitVer/GitVer_cli.cpp | 89 ++++ GitVer/GitVer_cli.h | 5 + GitVer/GitVer_common.cpp | 95 ++++ GitVer/GitVer_common.h | 8 + GitVer/GitVer_nuitka.cpp | 190 ++++++++ GitVer/GitVer_nuitka.h | 4 + GitVer/GitVer_process.cpp | 170 +++++++ GitVer/GitVer_process.h | 4 + GitVer/GitVer_rewrite.cpp | 442 +++++++++++++++++ GitVer/GitVer_rewrite.h | 3 + GitVer/GitVer_tag.cpp | 235 +++++++++ GitVer/GitVer_tag.h | 11 + GitVer/GitVer_types.h | 9 + GitVer/GitVer_version.cpp | 244 ++++++++++ GitVer/GitVer_version.h | 24 + GitVer/app.ico | Bin 0 -> 25214 bytes GitVer/framework.h | 26 + GitVer/pch.cpp | 5 + GitVer/pch.h | 13 + GitVer/resource.h | 18 + GitVer/targetver.h | 6 + 27 files changed, 2611 insertions(+), 25 deletions(-) create mode 100644 GitVer/GitVer.h create mode 100644 GitVer/GitVer.rc create mode 100644 GitVer/GitVer_cli.cpp create mode 100644 GitVer/GitVer_cli.h create mode 100644 GitVer/GitVer_common.cpp create mode 100644 GitVer/GitVer_common.h create mode 100644 GitVer/GitVer_nuitka.cpp create mode 100644 GitVer/GitVer_nuitka.h create mode 100644 GitVer/GitVer_process.cpp create mode 100644 GitVer/GitVer_process.h create mode 100644 GitVer/GitVer_rewrite.cpp create mode 100644 GitVer/GitVer_rewrite.h create mode 100644 GitVer/GitVer_tag.cpp create mode 100644 GitVer/GitVer_tag.h create mode 100644 GitVer/GitVer_types.h create mode 100644 GitVer/GitVer_version.cpp create mode 100644 GitVer/GitVer_version.h create mode 100644 GitVer/app.ico create mode 100644 GitVer/framework.h create mode 100644 GitVer/pch.cpp create mode 100644 GitVer/pch.h create mode 100644 GitVer/resource.h create mode 100644 GitVer/targetver.h diff --git a/GitVer.sln b/GitVer.sln index 75d8758..c230d56 100644 --- a/GitVer.sln +++ b/GitVer.sln @@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.14.37203.1 d17.14 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 Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -13,19 +13,19 @@ Global Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {8C09E04E-D13C-4D8F-86E1-3B2A712FA540}.Debug|x64.ActiveCfg = Debug|x64 - {8C09E04E-D13C-4D8F-86E1-3B2A712FA540}.Debug|x64.Build.0 = Debug|x64 - {8C09E04E-D13C-4D8F-86E1-3B2A712FA540}.Debug|x86.ActiveCfg = Debug|Win32 - {8C09E04E-D13C-4D8F-86E1-3B2A712FA540}.Debug|x86.Build.0 = Debug|Win32 - {8C09E04E-D13C-4D8F-86E1-3B2A712FA540}.Release|x64.ActiveCfg = Release|x64 - {8C09E04E-D13C-4D8F-86E1-3B2A712FA540}.Release|x64.Build.0 = Release|x64 - {8C09E04E-D13C-4D8F-86E1-3B2A712FA540}.Release|x86.ActiveCfg = Release|Win32 - {8C09E04E-D13C-4D8F-86E1-3B2A712FA540}.Release|x86.Build.0 = Release|Win32 + {480198B5-83D1-4EBB-85BB-FBA5F8A0061E}.Debug|x64.ActiveCfg = Debug|x64 + {480198B5-83D1-4EBB-85BB-FBA5F8A0061E}.Debug|x64.Build.0 = Debug|x64 + {480198B5-83D1-4EBB-85BB-FBA5F8A0061E}.Debug|x86.ActiveCfg = Debug|Win32 + {480198B5-83D1-4EBB-85BB-FBA5F8A0061E}.Debug|x86.Build.0 = Debug|Win32 + {480198B5-83D1-4EBB-85BB-FBA5F8A0061E}.Release|x64.ActiveCfg = Release|x64 + {480198B5-83D1-4EBB-85BB-FBA5F8A0061E}.Release|x64.Build.0 = Release|x64 + {480198B5-83D1-4EBB-85BB-FBA5F8A0061E}.Release|x86.ActiveCfg = Release|Win32 + {480198B5-83D1-4EBB-85BB-FBA5F8A0061E}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {4B466BE8-D922-416A-BE35-C3BB9F65B8E7} + SolutionGuid = {CD2858C1-027F-4A5B-8470-01D454B48B60} EndGlobalSection EndGlobal diff --git a/GitVer/GitVer.cpp b/GitVer/GitVer.cpp index 41a3140..88d5157 100644 --- a/GitVer/GitVer.cpp +++ b/GitVer/GitVer.cpp @@ -1,20 +1,872 @@ // GitVer.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 // -#include +#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[]); + +/// +/// 将宽字符字符串转换为ANSI字符串。 +/// +/// 要转换的宽字符字符串。 +/// 转换后的ANSI字符串。 +std::string ToAnsiString(LPCTSTR lpText) { - std::cout << "Hello World!\n"; + 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 } -// 运行程序: Ctrl + F5 或调试 >“开始执行(不调试)”菜单 -// 调试程序: F5 或调试 >“开始调试”菜单 +/// +/// 检查指定目录下是否存在与模式匹配的文件。 +/// +/// 要搜索的基目录。 +/// 要匹配的文件模式。 +/// 如果存在匹配的文件,则返回TRUE;否则返回FALSE。 +BOOL HasMatchingFile(LPCTSTR lpBaseDir, LPCTSTR lpPattern) +{ + CString strSearchPath; + strSearchPath.Format(_T("%s%s"), lpBaseDir, lpPattern); -// 入门使用技巧: -// 1. 使用解决方案资源管理器窗口添加/管理文件 -// 2. 使用团队资源管理器窗口连接到源代码管理 -// 3. 使用输出窗口查看生成输出和其他消息 -// 4. 使用错误列表窗口查看错误 -// 5. 转到“项目”>“添加新项”以创建新的代码文件,或转到“项目”>“添加现有项”以将现有代码文件添加到项目 -// 6. 将来,若要再次打开此项目,请转到“文件”>“打开”>“项目”并选择 .sln 文件 + WIN32_FIND_DATA findData = { 0 }; + HANDLE hFind = ::FindFirstFile(strSearchPath, &findData); + if (hFind == INVALID_HANDLE_VALUE) + { + return FALSE; + } + + ::FindClose(hFind); + return TRUE; +} + +/// +/// 递归查找与模式匹配的第一个文件。 +/// +/// 要搜索的基目录。 +/// 要匹配的文件模式。 +/// 找到的文件的完整路径。 +/// 如果找到匹配的文件,则返回TRUE;否则返回FALSE。 +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; +} + +/// +/// 构建相对于仓库的路径。 +/// +/// 基目录。 +/// 完整路径。 +/// 相对路径。 +/// 如果成功构建相对路径,则返回TRUE;否则返回FALSE。 +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(); +} + +/// +/// 获取文件名(不包含扩展名)。 +/// +/// 文件路径。 +/// 不包含扩展名的文件名。 +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; +} + +/// +/// 查找首选的C++资源文件(.rc)。 +/// +/// 要搜索的基目录。 +/// 找到的资源文件的完整路径。 +/// 如果找到首选的资源文件,则返回TRUE;否则返回FALSE。 +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); +} + +/// +/// 查找首选的AssemblyInfo.cs文件。 +/// +/// 要搜索的基目录。 +/// 找到的AssemblyInfo.cs文件的完整路径。 +/// 如果找到首选的AssemblyInfo.cs文件,则返回TRUE;否则返回FALSE。 +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); +} + +/// +/// 检测项目的源码类型。 +/// +/// 要检测的基目录。 +/// 返回项目的源码类型枚举值。 +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; +} + +/// +/// 获取项目源码类型的名称。 +/// +/// 项目源码类型枚举值。 +/// 项目源码类型的名称。 +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"); + } +} + +/// +/// 打印完整的使用示例。 +/// +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); +} + +/// +/// 打印命令简要用法(用于未知命令等场景)。 +/// +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")); +} + +/// +/// 打印命令参数不足时的标准用法与示例。 +/// +/// 命令名称。 +/// 必选参数占位文本。 +/// 示例中的必选参数。 +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); + } +} + +/// +/// 获取退出码的简要说明。 +/// +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(""); + } +} + +/// +/// 在命令执行返回非零时输出统一错误日志。 +/// +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); + } + } +} + +/// +/// 输出命令执行结束日志(含耗时和退出码)。 +/// +void LogCommandEnd(LPCTSTR lpCommandName, DWORD dwStartTick, int nRetCode) +{ + DWORD dwElapsed = GetTickCount() - dwStartTick; + _tprintf(_T("命令结束: %s,耗时 %u ms,退出码 %d\n"), lpCommandName, dwElapsed, nRetCode); +} + +/// +/// 获取当前模块的目录信息。 +/// +/// 返回值。 +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); +} + + +/// +/// 处理设置版本命令。 +/// +/// 参数个数。 +/// 参数数组。 +/// 返回值。 +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); +} + + +/// +/// +/// 准备C++重写内容。 +/// +/// 提交ID。 +/// PE类型。 +/// 资源文件路径。 +/// 旧内容向量。 +/// 新内容向量。 +/// +void PrepareCppRewriteContent(const CString& strCommitId, int nPEType, CString& strResFile, std::vector& vtOldContent, std::vector& 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 +} + + +/// +/// 准备C#重写内容。 +/// +/// 提交ID。 +/// 资源文件路径。 +/// 旧内容向量。 +/// 新内容向量。 +void PrepareCSharpRewriteContent(const CString& strCommitId, CString& strResFile, std::vector& vtOldContent, std::vector& 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)); +} + + +/// +/// 处理重写命令。 +/// +/// 参数个数。 +/// 参数数组。 +/// 返回值。 +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 vtOldContent; + std::vector 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; +} diff --git a/GitVer/GitVer.h b/GitVer/GitVer.h new file mode 100644 index 0000000..d00d47e --- /dev/null +++ b/GitVer/GitVer.h @@ -0,0 +1,3 @@ +#pragma once + +#include "resource.h" diff --git a/GitVer/GitVer.rc b/GitVer/GitVer.rc new file mode 100644 index 0000000000000000000000000000000000000000..028894ee4e9de971cd0b118c83cbaa4962f125c4 GIT binary patch literal 5406 zcmd6r&2A$_5Xb9`Se{{UF1unkiL)z&5JDn}V=Npy#Eu10Br7Cwh?F>%KOhJW-1fkO z@Ep7oLR?_^{kvVB8G9Ty-itMAS5J3Ub#;A@-~78}Tb9_NUE8@0?aoGwo{d@Sur{)Z zo!Jdwa@Mi*cSn} zv_QXU-&-kQi*h62^#YxEe_=<~vbLSrzSXR0T|2f4tNY9wR=00iY4Lk#yR6ii`}vY# z{T`RjVPaMEGOyzvTDr4a?mtF5(nFhDf5UH$TkmmC>7&B>4rAUT|8IjctbRYG%j^Zz!;O%l+q?yN14A+R^BQR>nTZ3wU?rzJiu9G_4?5i2odf7B9joa7Z zvdH#*L5(sxT$Tg0zsudLAv@?TnLfqY_SCkg8MlJwQavk|ta3(CmM>8;=5tKRPRTi^ zL~zXasg%nkdby%Rk3DB2MZqpY3RYc2^Tk;4Q}Ij6$(XDit| zq`Qgzh)z_45^DGzZ>s(37i#!TVn2j_VG=6T0F1NH5vWU01s_gv$519g!|E_ptr9Q? zJU3vSGamybO%mR-a>eR)3ywO85g1YK5$}>jwNvs$YIQH_Uq<%RboUk*>!BpBk;7_a zG4&2za?ufw+j|~5-u(KqcX_<-v^l*)+S9n!*UKkd8kf$YdO>x3(s0%i?jap=br+lp zH9=WB>QYg0z+BYX9bXr95;A+8{X9-Wc6&}vilZESGQl#(@Y|){A`4t9>(9!;Hz)Z zsd*1Ne(UChlRBF}Z(J_)zcjlN@+&-p1$QQh)ghaK#wo>w|3A-8(t0rA!+&Ktm6 zBrb(MqlB->Ynt{cxS!e0*MGO3v_D^RdNF_3!9HBG-!?HGiqCm{`B&r5-#+NAc2Yj# z{Dd;OWP3WwkGg@Zr`$LMDkskCsEU=x#FQ{L`MqZp+oxDVb?~nOXW3$a734ox)QR!T z4vRYp-RNHEH+Q(#j(z;7Gn>;hMvom2YGGtNSlwZRNI@`=Y$5 zwCPOy8eW^$54v;?^<|T0Pxa4dWIJV0#}L%ezZl2lRedw*i%Pbsgq`B7s(wY4Hg+jp zXle}6xV`hR1m85OV>IVAyS&SN`)|$lvxrONdUhCV{6qg1EOzqFYyF^ZC8->~>;Fdj hvU>P`BiG~l>B1$Fgv))pPZrmg!Q#I}i*^^M{{ZX1fdl{m literal 0 HcmV?d00001 diff --git a/GitVer/GitVer.vcxproj b/GitVer/GitVer.vcxproj index f1710e6..cdf7d51 100644 --- a/GitVer/GitVer.vcxproj +++ b/GitVer/GitVer.vcxproj @@ -21,7 +21,7 @@ 17.0 Win32Proj - {8c09e04e-d13c-4d8f-86e1-3b2a712fa540} + {480198b5-83d1-4ebb-85bb-fba5f8a0061e} GitVer 10.0 @@ -31,6 +31,7 @@ true v143 Unicode + Dynamic Application @@ -38,19 +39,22 @@ v143 true Unicode + Dynamic Application true v143 - Unicode + MultiByte + Dynamic Application false v143 true - Unicode + MultiByte + Static @@ -70,12 +74,23 @@ + + ..\..\..\..\bin\$(ProjectName)\ + $(OutDir)$(Configuration)\ + $(ProjectName)d + + + ..\..\..\..\bin\$(ProjectName)\ + $(OutDir)$(Configuration)\ + Level3 true WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) true + Use + pch.h Console @@ -90,6 +105,8 @@ true WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true + Use + pch.h Console @@ -102,6 +119,8 @@ true _DEBUG;_CONSOLE;%(PreprocessorDefinitions) true + Use + pch.h Console @@ -116,14 +135,50 @@ true NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true + Use + pch.h Console true + + + + + + + + + + + + + + + + + + + + + + + + Create + Create + Create + Create + + + + + + + diff --git a/GitVer/GitVer.vcxproj.filters b/GitVer/GitVer.vcxproj.filters index 535a5bb..cc8cf05 100644 --- a/GitVer/GitVer.vcxproj.filters +++ b/GitVer/GitVer.vcxproj.filters @@ -14,9 +14,84 @@ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + 源文件 + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + + + 资源文件 + + + + + 资源文件 + \ No newline at end of file diff --git a/GitVer/GitVer_cli.cpp b/GitVer/GitVer_cli.cpp new file mode 100644 index 0000000..5f700ee --- /dev/null +++ b/GitVer/GitVer_cli.cpp @@ -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(": ЧIJֿ· %s\n"), lpRepoPath == NULL ? _T("") : 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(": IJ %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; +} diff --git a/GitVer/GitVer_cli.h b/GitVer/GitVer_cli.h new file mode 100644 index 0000000..a1eb5d1 --- /dev/null +++ b/GitVer/GitVer_cli.h @@ -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); diff --git a/GitVer/GitVer_common.cpp b/GitVer/GitVer_common.cpp new file mode 100644 index 0000000..00829bf --- /dev/null +++ b/GitVer/GitVer_common.cpp @@ -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("."); +} diff --git a/GitVer/GitVer_common.h b/GitVer/GitVer_common.h new file mode 100644 index 0000000..f25b306 --- /dev/null +++ b/GitVer/GitVer_common.h @@ -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); diff --git a/GitVer/GitVer_nuitka.cpp b/GitVer/GitVer_nuitka.cpp new file mode 100644 index 0000000..d1e5e16 --- /dev/null +++ b/GitVer/GitVer_nuitka.cpp @@ -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); +} diff --git a/GitVer/GitVer_nuitka.h b/GitVer/GitVer_nuitka.h new file mode 100644 index 0000000..dd880d2 --- /dev/null +++ b/GitVer/GitVer_nuitka.h @@ -0,0 +1,4 @@ +#pragma once + +int HandleNuitkaBuildCommand(int argc, TCHAR* argv[]); +int HandleNuitkaPydBuildCommand(int argc, TCHAR* argv[]); diff --git a/GitVer/GitVer_process.cpp b/GitVer/GitVer_process.cpp new file mode 100644 index 0000000..c888e17 --- /dev/null +++ b/GitVer/GitVer_process.cpp @@ -0,0 +1,170 @@ +#include "pch.h" +#include "gitver.h" +#include "gitver_process.h" + +BOOL ReplaceFileContent(LPCTSTR lpFile, std::vector& vtOldContent, std::vector& 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::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::iterator it_old; + std::vector::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("") : program, + args == NULL ? _T("") : args, + lpCurrentDirectory == NULL ? _T("") : lpCurrentDirectory, + strValue.GetString()); + + return strValue; +} diff --git a/GitVer/GitVer_process.h b/GitVer/GitVer_process.h new file mode 100644 index 0000000..89b889d --- /dev/null +++ b/GitVer/GitVer_process.h @@ -0,0 +1,4 @@ +#pragma once + +BOOL ReplaceFileContent(LPCTSTR lpFile, std::vector& vtOldContent, std::vector& vtNewContent); +CString StartProcess(LPCTSTR program, LPCTSTR args, LPCTSTR lpCurrentDirectory); diff --git a/GitVer/GitVer_rewrite.cpp b/GitVer/GitVer_rewrite.cpp new file mode 100644 index 0000000..60867fd --- /dev/null +++ b/GitVer/GitVer_rewrite.cpp @@ -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); + +/// +/// 滻ָΧڰָǵݡ +/// +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("") : 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; +} diff --git a/GitVer/GitVer_rewrite.h b/GitVer/GitVer_rewrite.h new file mode 100644 index 0000000..a6a0fde --- /dev/null +++ b/GitVer/GitVer_rewrite.h @@ -0,0 +1,3 @@ +#pragma once + +int RewriteSetVerByProjectType(LPCTSTR lpRepoPath, const CString& strProductVersion, const CString& strFileVersion); diff --git a/GitVer/GitVer_tag.cpp b/GitVer/GitVer_tag.cpp new file mode 100644 index 0000000..b8f339e --- /dev/null +++ b/GitVer/GitVer_tag.cpp @@ -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& 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 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("") : lpRepoPath); + std::vector 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; +} diff --git a/GitVer/GitVer_tag.h b/GitVer/GitVer_tag.h new file mode 100644 index 0000000..3cf9e0d --- /dev/null +++ b/GitVer/GitVer_tag.h @@ -0,0 +1,11 @@ +#pragma once + +struct BranchTagInfo +{ + CString strTag; + UINT nMajor; + UINT nMinor; +}; + +BOOL GetRecentMajorMinorTags(LPCTSTR lpRepoPath, CString& strBranch, std::vector& vtTags, int nMaxCount); +int HandleCreateTagInteractive(); diff --git a/GitVer/GitVer_types.h b/GitVer/GitVer_types.h new file mode 100644 index 0000000..60b39ea --- /dev/null +++ b/GitVer/GitVer_types.h @@ -0,0 +1,9 @@ +#pragma once + +enum ProjectCodeType +{ + PROJECT_UNKNOWN = 0, + PROJECT_CPP = 1, + PROJECT_CSHARP = 2, + PROJECT_PYTHON = 3, +}; diff --git a/GitVer/GitVer_version.cpp b/GitVer/GitVer_version.cpp new file mode 100644 index 0000000..0be54bc --- /dev/null +++ b/GitVer/GitVer_version.cpp @@ -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("") : 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; +} diff --git a/GitVer/GitVer_version.h b/GitVer/GitVer_version.h new file mode 100644 index 0000000..d6a2ed0 --- /dev/null +++ b/GitVer/GitVer_version.h @@ -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); diff --git a/GitVer/app.ico b/GitVer/app.ico new file mode 100644 index 0000000000000000000000000000000000000000..51227301695c014f31ee13d9c79db54f93553f18 GIT binary patch literal 25214 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 literal 0 HcmV?d00001 diff --git a/GitVer/framework.h b/GitVer/framework.h new file mode 100644 index 0000000..aa87d07 --- /dev/null +++ b/GitVer/framework.h @@ -0,0 +1,26 @@ +#pragma once + +#include "targetver.h" +#include +#include +#define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS // 部分 CString 构造函数将是显式的 +#define _AFX_NO_MFC_CONTROLS_IN_DIALOGS // 移除对话框中的 MFC 控件支持 + +#ifndef VC_EXTRALEAN +#define VC_EXTRALEAN // 从 Windows 头文件中排除极少使用的内容 +#endif + +#include +#include // MFC 核心组件和标准组件 +#include // MFC 扩展 +#ifndef _AFX_NO_OLE_SUPPORT +#include // MFC 对 Internet Explorer 4 公共控件的支持 +#endif +#ifndef _AFX_NO_AFXCMN_SUPPORT +#include // MFC 对 Windows 公共控件的支持 +#endif // _AFX_NO_AFXCMN_SUPPORT + +#include +#include +#include +#include \ No newline at end of file diff --git a/GitVer/pch.cpp b/GitVer/pch.cpp new file mode 100644 index 0000000..b6fb8f4 --- /dev/null +++ b/GitVer/pch.cpp @@ -0,0 +1,5 @@ +// pch.cpp: 与预编译标头对应的源文件 + +#include "pch.h" + +// 当使用预编译的头时,需要使用此源文件,编译才能成功。 diff --git a/GitVer/pch.h b/GitVer/pch.h new file mode 100644 index 0000000..9660927 --- /dev/null +++ b/GitVer/pch.h @@ -0,0 +1,13 @@ +// pch.h: 这是预编译标头文件。 +// 下方列出的文件仅编译一次,提高了将来生成的生成性能。 +// 这还将影响 IntelliSense 性能,包括代码完成和许多代码浏览功能。 +// 但是,如果此处列出的文件中的任何一个在生成之间有更新,它们全部都将被重新编译。 +// 请勿在此处添加要频繁更新的文件,这将使得性能优势无效。 + +#ifndef PCH_H +#define PCH_H + +// 添加要在此处预编译的标头 +#include "framework.h" + +#endif //PCH_H diff --git a/GitVer/resource.h b/GitVer/resource.h new file mode 100644 index 0000000..037819f --- /dev/null +++ b/GitVer/resource.h @@ -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 diff --git a/GitVer/targetver.h b/GitVer/targetver.h new file mode 100644 index 0000000..a186354 --- /dev/null +++ b/GitVer/targetver.h @@ -0,0 +1,6 @@ +#pragma once + +// // 包含 SDKDDKVer.h 可定义可用的最高版本的 Windows 平台。 +// 如果希望为之前的 Windows 平台构建应用程序,在包含 SDKDDKVer.h 之前请先包含 WinSDKVer.h 并 +// 将 _WIN32_WINNT 宏设置为想要支持的平台。 +#include