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 0000000..028894e Binary files /dev/null and b/GitVer/GitVer.rc differ 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 0000000..5122730 Binary files /dev/null and b/GitVer/app.ico differ 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