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