Compare commits
13 Commits
84d67d9709
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 0766d09e27 | |||
| 6b0f602e57 | |||
| ec7b51873c | |||
| 685a2094f7 | |||
| 04904c2919 | |||
| 46134343e3 | |||
| 5679433f7c | |||
| 71f99a55b4 | |||
| a1f4ebd45a | |||
| 171fa4448d | |||
| c4d2431cb0 | |||
| 67d24d69f5 | |||
| 4a4c51d124 |
@@ -14,6 +14,7 @@
|
|||||||
#include "gitver_types.h"
|
#include "gitver_types.h"
|
||||||
#include "gitver_version.h"
|
#include "gitver_version.h"
|
||||||
#include "gitver_setup.h"
|
#include "gitver_setup.h"
|
||||||
|
#include "gitver_upload.h"
|
||||||
|
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
#define new DEBUG_NEW
|
#define new DEBUG_NEW
|
||||||
@@ -354,9 +355,11 @@ void PrintFullUsageExamples()
|
|||||||
_T(" gitver nuitkabuild=<pid> [repodir=<path>可选] [-test可选] [params=\"<mainPy> <nuitka参数>\"可选]\n")
|
_T(" gitver nuitkabuild=<pid> [repodir=<path>可选] [-test可选] [params=\"<mainPy> <nuitka参数>\"可选]\n")
|
||||||
_T(" gitver nuitkapydbuild=<pid> [repodir=<path>可选] [-test可选] [params=\"<modulePy> <nuitka参数>\"可选]\n")
|
_T(" gitver nuitkapydbuild=<pid> [repodir=<path>可选] [-test可选] [params=\"<modulePy> <nuitka参数>\"可选]\n")
|
||||||
_T(" gitver pyinstaller=<pid> [repodir=<path>可选] [-test可选] [params=\"<mainPy|specFile> <pyinstaller参数>\"可选]\n")
|
_T(" gitver pyinstaller=<pid> [repodir=<path>可选] [-test可选] [params=\"<mainPy|specFile> <pyinstaller参数>\"可选]\n")
|
||||||
_T(" gitver -setup=0|1 [pid] [repodir=<path>可选] [-test可选]\n")
|
_T(" gitver setup=n [pid=m可选] [repodir=<path>可选] [-test可选]\n")
|
||||||
_T(" -setup=0: 使用 Inno Setup 脚本 (setup.iss)\n")
|
_T(" gitver upload username=<用户名> [file=<上传文件>] [info=<配置.txt>] [url=<PMC基址>]\n")
|
||||||
_T(" -setup=1: 使用 NSIS 脚本 (setup.nsh)\n")
|
_T(" setup=0: 使用 Inno Setup 脚本 (setup.iss)\n")
|
||||||
|
_T(" setup=1: 使用 NSIS 脚本 (setup.nsi)\n")
|
||||||
|
_T(" 不指定 pid 时自动从 .rc / AssemblyInfo.cs / version_info.txt 读取版本\n")
|
||||||
_T("\n示例:\n")
|
_T("\n示例:\n")
|
||||||
_T(" gitver\n")
|
_T(" gitver\n")
|
||||||
_T(" gitver rewrite\n")
|
_T(" gitver rewrite\n")
|
||||||
@@ -377,8 +380,10 @@ void PrintFullUsageExamples()
|
|||||||
_T(" gitver pyinstaller=5 params=\"myapp.spec\"\n")
|
_T(" gitver pyinstaller=5 params=\"myapp.spec\"\n")
|
||||||
_T(" gitver pyinstaller=5 -test params=\"myapp.spec\"\n")
|
_T(" gitver pyinstaller=5 -test params=\"myapp.spec\"\n")
|
||||||
_T(" gitver pyinstaller=5 repodir=E:\\Code\\MyPyProj params=\"myapp.spec\"\n")
|
_T(" gitver pyinstaller=5 repodir=E:\\Code\\MyPyProj params=\"myapp.spec\"\n")
|
||||||
_T(" gitver -setup=0 5\n")
|
_T(" gitver setup=0\n")
|
||||||
_T(" gitver -setup=1 5 repodir=E:\\Code\\MyProj\n")
|
_T(" gitver setup=0 pid=5\n")
|
||||||
|
_T(" gitver setup=1 pid=5 repodir=E:\\Code\\MyProj\n")
|
||||||
|
_T(" gitver upload username=admin\n")
|
||||||
_T("params: 传递给 Nuitka/PyInstaller 的参数(含入口文件),用双引号括起来,如 params=\"main.py --standalone --output-dir=dist\"。\n")
|
_T("params: 传递给 Nuitka/PyInstaller 的参数(含入口文件),用双引号括起来,如 params=\"main.py --standalone --output-dir=dist\"。\n")
|
||||||
_T("-test: 将产品版本号的 major 和 minor 都置为 0(用于测试版本构建)。\n")
|
_T("-test: 将产品版本号的 major 和 minor 都置为 0(用于测试版本构建)。\n")
|
||||||
_T("无参数时会读取当前分支最近三次 tag,并提示选择 major 加 1 或 minor 加 1,然后创建新 tag。\n")
|
_T("无参数时会读取当前分支最近三次 tag,并提示选择 major 加 1 或 minor 加 1,然后创建新 tag。\n")
|
||||||
@@ -397,6 +402,7 @@ void PrintShortCommandUsage()
|
|||||||
_tprintf(_T("或:gitver nuitkabuild=<pid> [repodir=<path>可选] [params=\"<mainPy> <nuitka参数>\"可选]\n"));
|
_tprintf(_T("或:gitver nuitkabuild=<pid> [repodir=<path>可选] [params=\"<mainPy> <nuitka参数>\"可选]\n"));
|
||||||
_tprintf(_T("或:gitver nuitkapydbuild=<pid> [repodir=<path>可选] [params=\"<modulePy> <nuitka参数>\"可选]\n"));
|
_tprintf(_T("或:gitver nuitkapydbuild=<pid> [repodir=<path>可选] [params=\"<modulePy> <nuitka参数>\"可选]\n"));
|
||||||
_tprintf(_T("或:gitver pyinstaller=<pid> [repodir=<path>可选] [params=\"<mainPy|specFile> <pyinstaller参数>\"可选]\n"));
|
_tprintf(_T("或:gitver pyinstaller=<pid> [repodir=<path>可选] [params=\"<mainPy|specFile> <pyinstaller参数>\"可选]\n"));
|
||||||
|
_tprintf(_T("或:gitver upload username=<用户名> [file=<上传文件>] [info=<配置.txt>] [url=<PMC基址>]\n"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -484,7 +490,7 @@ LPCTSTR GetExitCodeHint(int nRetCode)
|
|||||||
case 35:
|
case 35:
|
||||||
return _T("rewrite version file failed");
|
return _T("rewrite version file failed");
|
||||||
case 36:
|
case 36:
|
||||||
return _T("setup: unsupported -setup value");
|
return _T("setup: unsupported setup value");
|
||||||
case 37:
|
case 37:
|
||||||
return _T("setup: setup script not found");
|
return _T("setup: setup script not found");
|
||||||
case 38:
|
case 38:
|
||||||
@@ -505,6 +511,20 @@ LPCTSTR GetExitCodeHint(int nRetCode)
|
|||||||
return _T("pyinstaller: write version_info.txt failed");
|
return _T("pyinstaller: write version_info.txt failed");
|
||||||
case 46:
|
case 46:
|
||||||
return _T("pyinstaller: inject version into spec file failed");
|
return _T("pyinstaller: inject version into spec file failed");
|
||||||
|
case 47:
|
||||||
|
return _T("upload: missing required args");
|
||||||
|
case 48:
|
||||||
|
return _T("upload: file not found");
|
||||||
|
case 49:
|
||||||
|
return _T("upload: info invalid or env password missing");
|
||||||
|
case 50:
|
||||||
|
return _T("upload: login failed");
|
||||||
|
case 51:
|
||||||
|
return _T("upload: upload http failed");
|
||||||
|
case 52:
|
||||||
|
return _T("upload: version not found after upload");
|
||||||
|
case 53:
|
||||||
|
return _T("upload: network error");
|
||||||
default:
|
default:
|
||||||
return _T("");
|
return _T("");
|
||||||
}
|
}
|
||||||
@@ -603,6 +623,8 @@ int HandleSetVerCommand(int argc, TCHAR* argv[])
|
|||||||
|
|
||||||
CString strProductVersion;
|
CString strProductVersion;
|
||||||
CString strFileVersion;
|
CString strFileVersion;
|
||||||
|
CString strBranch;
|
||||||
|
SYSTEMTIME stFileVersionDate = { 0 };
|
||||||
VersionBuildErrorCodes errorCodes = { 5, 6, 9 };
|
VersionBuildErrorCodes errorCodes = { 5, 6, 9 };
|
||||||
int nVersionRet = BuildVersionsFromRepo(
|
int nVersionRet = BuildVersionsFromRepo(
|
||||||
lpRepoPath,
|
lpRepoPath,
|
||||||
@@ -611,7 +633,9 @@ int HandleSetVerCommand(int argc, TCHAR* argv[])
|
|||||||
strProductVersion,
|
strProductVersion,
|
||||||
strFileVersion,
|
strFileVersion,
|
||||||
DEFAULT_MAJOR_WHEN_NO_TAG,
|
DEFAULT_MAJOR_WHEN_NO_TAG,
|
||||||
DEFAULT_MINOR_WHEN_NO_TAG);
|
DEFAULT_MINOR_WHEN_NO_TAG,
|
||||||
|
&strBranch,
|
||||||
|
&stFileVersionDate);
|
||||||
if (nVersionRet != 0)
|
if (nVersionRet != 0)
|
||||||
{
|
{
|
||||||
return nVersionRet;
|
return nVersionRet;
|
||||||
@@ -631,39 +655,23 @@ int HandleSetVerCommand(int argc, TCHAR* argv[])
|
|||||||
_tprintf(_T("ProductVersion=%s\n"), strProductVersion.GetString());
|
_tprintf(_T("ProductVersion=%s\n"), strProductVersion.GetString());
|
||||||
_tprintf(_T("FileVersion=%s\n"), strFileVersion.GetString());
|
_tprintf(_T("FileVersion=%s\n"), strFileVersion.GetString());
|
||||||
|
|
||||||
// 检查是否附带了 -setup=N 标志
|
CString strChangeLog;
|
||||||
int nSetupType = -1;
|
if (!TryGetGitLogByBranchDate(lpRepoPath, strBranch, stFileVersionDate, strChangeLog))
|
||||||
for (int i = 2; i < argc; ++i)
|
|
||||||
{
|
{
|
||||||
CString strArg = argv[i];
|
strChangeLog = _T("无当日提交记录");
|
||||||
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)
|
TCHAR szInfoPath[MAX_PATH] = { 0 };
|
||||||
|
_tcscpy_s(szInfoPath, lpRepoPath);
|
||||||
|
PathAppend(szInfoPath, _T("upload_info.txt"));
|
||||||
|
if (!WriteUploadInfoVersions(szInfoPath, strProductVersion, strFileVersion, strChangeLog))
|
||||||
{
|
{
|
||||||
// -setup=N 存在:不回写版本信息到项目文件,只修改安装脚本并编译
|
_tprintf(_T("警告: 未能写入 upload_info.txt\n"));
|
||||||
if (!ExecuteSetupBuild(nSetupType, strProductVersion, strFileVersion))
|
|
||||||
{
|
|
||||||
return 38;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return RewriteSetVerByProjectType(lpRepoPath, strProductVersion, strFileVersion);
|
return RewriteSetVerByProjectType(lpRepoPath, strProductVersion, strFileVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 准备C++重写内容。
|
/// 准备C++重写内容。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -845,6 +853,14 @@ int HandleRewriteCommand(int argc, TCHAR* argv[])
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 程序入口:解析命令行参数并分发到各子命令处理函数。
|
||||||
|
/// 无参数时进入交互式 tag 创建模式。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="argc">参数个数。</param>
|
||||||
|
/// <param name="argv">参数数组。</param>
|
||||||
|
/// <param name="envp">环境变量数组。</param>
|
||||||
|
/// <returns>命令执行退出码,0 表示成功。</returns>
|
||||||
int main(int argc, TCHAR* argv[], TCHAR* envp[])
|
int main(int argc, TCHAR* argv[], TCHAR* envp[])
|
||||||
{
|
{
|
||||||
int nRetCode = 0;
|
int nRetCode = 0;
|
||||||
@@ -898,6 +914,9 @@ int main(int argc, TCHAR* argv[], TCHAR* envp[])
|
|||||||
|
|
||||||
if (argc >= 2 && _tcsnicmp(argv[1], _T("setver="), 7) == 0)
|
if (argc >= 2 && _tcsnicmp(argv[1], _T("setver="), 7) == 0)
|
||||||
{
|
{
|
||||||
|
#ifdef _DEBUG
|
||||||
|
Sleep(10000);
|
||||||
|
#endif
|
||||||
DWORD dwStartTick = LogCommandStart(_T("setver"));
|
DWORD dwStartTick = LogCommandStart(_T("setver"));
|
||||||
int nCmdRet = HandleSetVerCommand(argc, argv);
|
int nCmdRet = HandleSetVerCommand(argc, argv);
|
||||||
LogCommandEnd(_T("setver"), dwStartTick, nCmdRet);
|
LogCommandEnd(_T("setver"), dwStartTick, nCmdRet);
|
||||||
@@ -923,12 +942,24 @@ int main(int argc, TCHAR* argv[], TCHAR* envp[])
|
|||||||
return nCmdRet;
|
return nCmdRet;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argc >= 2 && _tcsnicmp(argv[1], _T("-setup="), 7) == 0)
|
if (argc >= 2 && _tcsnicmp(argv[1], _T("setup="), 6) == 0)
|
||||||
{
|
{
|
||||||
DWORD dwStartTick = LogCommandStart(_T("-setup"));
|
DWORD dwStartTick = LogCommandStart(_T("setup"));
|
||||||
int nCmdRet = HandleSetupCommand(argc, argv);
|
int nCmdRet = HandleSetupCommand(argc, argv);
|
||||||
LogCommandEnd(_T("-setup"), dwStartTick, nCmdRet);
|
LogCommandEnd(_T("setup"), dwStartTick, nCmdRet);
|
||||||
PrintCommandFailedWithCode(_T("-setup"), nCmdRet);
|
PrintCommandFailedWithCode(_T("setup"), nCmdRet);
|
||||||
|
return nCmdRet;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argc >= 2 && _tcsicmp(argv[1], _T("upload")) == 0)
|
||||||
|
{
|
||||||
|
#ifdef _DEBUG
|
||||||
|
Sleep(10000);
|
||||||
|
#endif
|
||||||
|
DWORD dwStartTick = LogCommandStart(_T("upload"));
|
||||||
|
int nCmdRet = HandleUploadCommand(argc, argv);
|
||||||
|
LogCommandEnd(_T("upload"), dwStartTick, nCmdRet);
|
||||||
|
PrintCommandFailedWithCode(_T("upload"), nCmdRet);
|
||||||
return nCmdRet;
|
return nCmdRet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
BIN
GitVer/GitVer.rc
BIN
GitVer/GitVer.rc
Binary file not shown.
@@ -77,7 +77,7 @@
|
|||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||||
<OutDir>..\..\..\..\bin\$(ProjectName)\</OutDir>
|
<OutDir>..\..\..\..\bin\$(ProjectName)\</OutDir>
|
||||||
<IntDir>$(OutDir)$(Configuration)\</IntDir>
|
<IntDir>$(OutDir)$(Configuration)\</IntDir>
|
||||||
<TargetName>$(ProjectName)d</TargetName>
|
<TargetName>$(ProjectName)</TargetName>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||||
<OutDir>..\..\..\..\bin\$(ProjectName)\</OutDir>
|
<OutDir>..\..\..\..\bin\$(ProjectName)\</OutDir>
|
||||||
@@ -153,9 +153,11 @@
|
|||||||
<ClInclude Include="GitVer_process.h" />
|
<ClInclude Include="GitVer_process.h" />
|
||||||
<ClInclude Include="GitVer_rewrite.h" />
|
<ClInclude Include="GitVer_rewrite.h" />
|
||||||
<ClInclude Include="GitVer_setup.h" />
|
<ClInclude Include="GitVer_setup.h" />
|
||||||
|
<ClInclude Include="GitVer_upload.h" />
|
||||||
<ClInclude Include="GitVer_tag.h" />
|
<ClInclude Include="GitVer_tag.h" />
|
||||||
<ClInclude Include="GitVer_types.h" />
|
<ClInclude Include="GitVer_types.h" />
|
||||||
<ClInclude Include="GitVer_version.h" />
|
<ClInclude Include="GitVer_version.h" />
|
||||||
|
<ClInclude Include="httplib.h" />
|
||||||
<ClInclude Include="pch.h" />
|
<ClInclude Include="pch.h" />
|
||||||
<ClInclude Include="Resource.h" />
|
<ClInclude Include="Resource.h" />
|
||||||
<ClInclude Include="targetver.h" />
|
<ClInclude Include="targetver.h" />
|
||||||
@@ -169,6 +171,7 @@
|
|||||||
<ClCompile Include="GitVer_process.cpp" />
|
<ClCompile Include="GitVer_process.cpp" />
|
||||||
<ClCompile Include="GitVer_rewrite.cpp" />
|
<ClCompile Include="GitVer_rewrite.cpp" />
|
||||||
<ClCompile Include="GitVer_setup.cpp" />
|
<ClCompile Include="GitVer_setup.cpp" />
|
||||||
|
<ClCompile Include="GitVer_upload.cpp" />
|
||||||
<ClCompile Include="GitVer_tag.cpp" />
|
<ClCompile Include="GitVer_tag.cpp" />
|
||||||
<ClCompile Include="GitVer_version.cpp" />
|
<ClCompile Include="GitVer_version.cpp" />
|
||||||
<ClCompile Include="pch.cpp">
|
<ClCompile Include="pch.cpp">
|
||||||
|
|||||||
@@ -57,6 +57,15 @@
|
|||||||
<ClInclude Include="GitVer_version.h">
|
<ClInclude Include="GitVer_version.h">
|
||||||
<Filter>头文件</Filter>
|
<Filter>头文件</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="GitVer_setup.h">
|
||||||
|
<Filter>头文件</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="GitVer_upload.h">
|
||||||
|
<Filter>头文件</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="httplib.h">
|
||||||
|
<Filter>头文件</Filter>
|
||||||
|
</ClInclude>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="GitVer.cpp">
|
<ClCompile Include="GitVer.cpp">
|
||||||
@@ -89,6 +98,12 @@
|
|||||||
<ClCompile Include="GitVer_version.cpp">
|
<ClCompile Include="GitVer_version.cpp">
|
||||||
<Filter>源文件</Filter>
|
<Filter>源文件</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="GitVer_setup.cpp">
|
||||||
|
<Filter>源文件</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="GitVer_upload.cpp">
|
||||||
|
<Filter>源文件</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ResourceCompile Include="GitVer.rc">
|
<ResourceCompile Include="GitVer.rc">
|
||||||
|
|||||||
@@ -5,11 +5,24 @@
|
|||||||
|
|
||||||
extern TCHAR g_szCurModuleDir[MAX_PATH];
|
extern TCHAR g_szCurModuleDir[MAX_PATH];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 打印无效仓库路径的错误信息。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lpRepoPath">无效的路径字符串。</param>
|
||||||
void PrintInvalidRepoPathError(LPCTSTR lpRepoPath)
|
void PrintInvalidRepoPathError(LPCTSTR lpRepoPath)
|
||||||
{
|
{
|
||||||
_tprintf(_T("错误: 无效的仓库路径 %s\n"), lpRepoPath == NULL ? _T("<null>") : lpRepoPath);
|
_tprintf(_T("错误: 无效的仓库路径 %s\n"), lpRepoPath == NULL ? _T("<null>") : lpRepoPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 解析 setver / nuitka / pyinstaller 等命令的 repodir= 与 -test 可选参数。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="argc">参数个数。</param>
|
||||||
|
/// <param name="argv">参数数组。</param>
|
||||||
|
/// <param name="nStartIndex">开始解析的下标。</param>
|
||||||
|
/// <param name="lpRepoPath">输出:仓库路径,未指定时为当前模块目录。</param>
|
||||||
|
/// <param name="bTestMode">输出:是否启用测试模式。</param>
|
||||||
|
/// <returns>0 成功;3 多余参数;23 无效 repodir。</returns>
|
||||||
int ParseSetVerOptions(int argc, TCHAR* argv[], int nStartIndex, LPCTSTR& lpRepoPath, BOOL& bTestMode)
|
int ParseSetVerOptions(int argc, TCHAR* argv[], int nStartIndex, LPCTSTR& lpRepoPath, BOOL& bTestMode)
|
||||||
{
|
{
|
||||||
lpRepoPath = NULL;
|
lpRepoPath = NULL;
|
||||||
@@ -24,12 +37,6 @@ int ParseSetVerOptions(int argc, TCHAR* argv[], int nStartIndex, LPCTSTR& lpRepo
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -setup=N 由调用方单独处理,此处静默跳过
|
|
||||||
if (strArg.GetLength() > 7 && strArg.Left(7).CompareNoCase(_T("-setup=")) == 0)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strArg.GetLength() > 8 && strArg.Left(8).CompareNoCase(_T("repodir=")) == 0)
|
if (strArg.GetLength() > 8 && strArg.Left(8).CompareNoCase(_T("repodir=")) == 0)
|
||||||
{
|
{
|
||||||
if (lpRepoPath != NULL)
|
if (lpRepoPath != NULL)
|
||||||
@@ -59,6 +66,14 @@ int ParseSetVerOptions(int argc, TCHAR* argv[], int nStartIndex, LPCTSTR& lpRepo
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 解析 rewrite 命令的可选 PE 类型(1=exe,2=dll)与 -f 强制标志。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="argc">参数个数。</param>
|
||||||
|
/// <param name="argv">参数数组(argv[0] 为 "rewrite")。</param>
|
||||||
|
/// <param name="nPEType">输出:PE 类型,默认 1。</param>
|
||||||
|
/// <param name="bForceRewrite">输出:是否忽略重写失败。</param>
|
||||||
|
/// <returns>0 成功;22 PE 类型无效或重复。</returns>
|
||||||
int ParseRewriteOptions(int argc, TCHAR* argv[], int& nPEType, BOOL& bForceRewrite)
|
int ParseRewriteOptions(int argc, TCHAR* argv[], int& nPEType, BOOL& bForceRewrite)
|
||||||
{
|
{
|
||||||
nPEType = 1;
|
nPEType = 1;
|
||||||
|
|||||||
@@ -1,5 +1,16 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 打印无效仓库路径的错误信息。
|
||||||
|
/// </summary>
|
||||||
void PrintInvalidRepoPathError(LPCTSTR lpRepoPath);
|
void PrintInvalidRepoPathError(LPCTSTR lpRepoPath);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 解析 setver 等命令的 repodir= 与 -test 可选参数。
|
||||||
|
/// </summary>
|
||||||
int ParseSetVerOptions(int argc, TCHAR* argv[], int nStartIndex, LPCTSTR& lpRepoPath, BOOL& bTestMode);
|
int ParseSetVerOptions(int argc, TCHAR* argv[], int nStartIndex, LPCTSTR& lpRepoPath, BOOL& bTestMode);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 解析 rewrite 命令的 PE 类型与 -f 强制标志。
|
||||||
|
/// </summary>
|
||||||
int ParseRewriteOptions(int argc, TCHAR* argv[], int& nPEType, BOOL& bForceRewrite);
|
int ParseRewriteOptions(int argc, TCHAR* argv[], int& nPEType, BOOL& bForceRewrite);
|
||||||
|
|||||||
@@ -2,6 +2,12 @@
|
|||||||
#include "gitver.h"
|
#include "gitver.h"
|
||||||
#include "gitver_common.h"
|
#include "gitver_common.h"
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将命令行参数拼接为便于日志输出的字符串(含引号转义)。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="argc">参数个数。</param>
|
||||||
|
/// <param name="argv">参数数组。</param>
|
||||||
|
/// <returns>空格分隔、已转义的参数字符串。</returns>
|
||||||
CString BuildArgsForLog(int argc, TCHAR* argv[])
|
CString BuildArgsForLog(int argc, TCHAR* argv[])
|
||||||
{
|
{
|
||||||
CString strArgs;
|
CString strArgs;
|
||||||
@@ -16,6 +22,11 @@ CString BuildArgsForLog(int argc, TCHAR* argv[])
|
|||||||
return strArgs;
|
return strArgs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 记录命令开始时间并输出开始日志。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lpCommandName">命令名称。</param>
|
||||||
|
/// <returns>当前 GetTickCount 值,供 LogCommandEnd 计算耗时。</returns>
|
||||||
DWORD LogCommandStart(LPCTSTR lpCommandName)
|
DWORD LogCommandStart(LPCTSTR lpCommandName)
|
||||||
{
|
{
|
||||||
DWORD dwStartTick = GetTickCount();
|
DWORD dwStartTick = GetTickCount();
|
||||||
@@ -23,6 +34,11 @@ DWORD LogCommandStart(LPCTSTR lpCommandName)
|
|||||||
return dwStartTick;
|
return dwStartTick;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 为命令行参数添加引号转义(含空格、制表符或双引号时加引号)。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="strArg">原始参数字符串。</param>
|
||||||
|
/// <returns>可直接用于命令行的参数字符串。</returns>
|
||||||
CString QuoteCmdArg(const CString& strArg)
|
CString QuoteCmdArg(const CString& strArg)
|
||||||
{
|
{
|
||||||
if (strArg.Find(_T(' ')) == -1 && strArg.Find(_T('\t')) == -1 && strArg.Find(_T('"')) == -1)
|
if (strArg.Find(_T(' ')) == -1 && strArg.Find(_T('\t')) == -1 && strArg.Find(_T('"')) == -1)
|
||||||
@@ -37,6 +53,12 @@ CString QuoteCmdArg(const CString& strArg)
|
|||||||
return strQuoted;
|
return strQuoted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 尝试将字符串解析为 0-65535 范围内的无符号整数。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="strValue">待解析字符串。</param>
|
||||||
|
/// <param name="nValue">输出:解析结果。</param>
|
||||||
|
/// <returns>解析成功返回 TRUE。</returns>
|
||||||
BOOL TryParseUInt16(const CString& strValue, UINT& nValue)
|
BOOL TryParseUInt16(const CString& strValue, UINT& nValue)
|
||||||
{
|
{
|
||||||
CString strTrimmed = strValue;
|
CString strTrimmed = strValue;
|
||||||
@@ -64,6 +86,15 @@ BOOL TryParseUInt16(const CString& strValue, UINT& nValue)
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 解析 argv[nIndex] 为 UInt16,失败时打印错误并返回指定错误码。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="argv">参数数组。</param>
|
||||||
|
/// <param name="nIndex">要解析的参数下标。</param>
|
||||||
|
/// <param name="lpArgName">参数名(用于错误提示)。</param>
|
||||||
|
/// <param name="nValue">输出:解析结果。</param>
|
||||||
|
/// <param name="nErrorCode">解析失败时的退出码。</param>
|
||||||
|
/// <returns>0 成功,否则返回 nErrorCode。</returns>
|
||||||
int ParseUInt16ArgOrError(TCHAR* argv[], int nIndex, LPCTSTR lpArgName, UINT& nValue, int nErrorCode)
|
int ParseUInt16ArgOrError(TCHAR* argv[], int nIndex, LPCTSTR lpArgName, UINT& nValue, int nErrorCode)
|
||||||
{
|
{
|
||||||
if (!TryParseUInt16(argv[nIndex], nValue))
|
if (!TryParseUInt16(argv[nIndex], nValue))
|
||||||
@@ -75,6 +106,11 @@ int ParseUInt16ArgOrError(TCHAR* argv[], int nIndex, LPCTSTR lpArgName, UINT& nV
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 判断参数字符串是否像路径(含 \、/、: 或以 . 开头)。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lpArg">待判断的参数字符串。</param>
|
||||||
|
/// <returns>像路径则返回 TRUE。</returns>
|
||||||
BOOL IsLikelyPathArg(LPCTSTR lpArg)
|
BOOL IsLikelyPathArg(LPCTSTR lpArg)
|
||||||
{
|
{
|
||||||
if (lpArg == NULL)
|
if (lpArg == NULL)
|
||||||
|
|||||||
@@ -1,8 +1,31 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将命令行参数拼接为便于日志输出的字符串。
|
||||||
|
/// </summary>
|
||||||
CString BuildArgsForLog(int argc, TCHAR* argv[]);
|
CString BuildArgsForLog(int argc, TCHAR* argv[]);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 记录命令开始时间并输出开始日志,返回 tick 供计算耗时。
|
||||||
|
/// </summary>
|
||||||
DWORD LogCommandStart(LPCTSTR lpCommandName);
|
DWORD LogCommandStart(LPCTSTR lpCommandName);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 为命令行参数添加引号转义。
|
||||||
|
/// </summary>
|
||||||
CString QuoteCmdArg(const CString& strArg);
|
CString QuoteCmdArg(const CString& strArg);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 尝试将字符串解析为 0-65535 范围内的无符号整数。
|
||||||
|
/// </summary>
|
||||||
BOOL TryParseUInt16(const CString& strValue, UINT& nValue);
|
BOOL TryParseUInt16(const CString& strValue, UINT& nValue);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 解析 argv[nIndex] 为 UInt16,失败时打印错误并返回指定错误码。
|
||||||
|
/// </summary>
|
||||||
int ParseUInt16ArgOrError(TCHAR* argv[], int nIndex, LPCTSTR lpArgName, UINT& nValue, int nErrorCode);
|
int ParseUInt16ArgOrError(TCHAR* argv[], int nIndex, LPCTSTR lpArgName, UINT& nValue, int nErrorCode);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 判断参数字符串是否像路径。
|
||||||
|
/// </summary>
|
||||||
BOOL IsLikelyPathArg(LPCTSTR lpArg);
|
BOOL IsLikelyPathArg(LPCTSTR lpArg);
|
||||||
|
|||||||
@@ -11,6 +11,15 @@ extern TCHAR g_szCurModuleDir[MAX_PATH];
|
|||||||
const UINT DEFAULT_MAJOR_WHEN_NO_TAG_FOR_NUITKA = 1;
|
const UINT DEFAULT_MAJOR_WHEN_NO_TAG_FOR_NUITKA = 1;
|
||||||
const UINT DEFAULT_MINOR_WHEN_NO_TAG_FOR_NUITKA = 0;
|
const UINT DEFAULT_MINOR_WHEN_NO_TAG_FOR_NUITKA = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 解析 nuitkabuild / nuitkapydbuild 命令的 repodir=、params= 与 -test 参数。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="argc">参数个数。</param>
|
||||||
|
/// <param name="argv">参数数组。</param>
|
||||||
|
/// <param name="lpRepoPath">输出:仓库路径。</param>
|
||||||
|
/// <param name="strExtraArgs">输出:传给 Nuitka 的额外参数。</param>
|
||||||
|
/// <param name="bTestMode">输出:是否测试模式。</param>
|
||||||
|
/// <returns>0 成功;3 未知/重复参数;23 无效 repodir。</returns>
|
||||||
static int ParseNuitkaOptions(int argc, TCHAR* argv[], LPCTSTR& lpRepoPath, CString& strExtraArgs, BOOL& bTestMode)
|
static int ParseNuitkaOptions(int argc, TCHAR* argv[], LPCTSTR& lpRepoPath, CString& strExtraArgs, BOOL& bTestMode)
|
||||||
{
|
{
|
||||||
lpRepoPath = NULL;
|
lpRepoPath = NULL;
|
||||||
@@ -71,6 +80,14 @@ static int ParseNuitkaOptions(int argc, TCHAR* argv[], LPCTSTR& lpRepoPath, CStr
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 执行 Nuitka 构建命令并输出版本信息与构建结果。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lpStartMessage">构建开始提示语。</param>
|
||||||
|
/// <param name="strNuitkaCmd">完整 Nuitka 命令行。</param>
|
||||||
|
/// <param name="strProductVersion">产品版本号(仅日志)。</param>
|
||||||
|
/// <param name="strFileVersion">文件版本号(仅日志)。</param>
|
||||||
|
/// <param name="lpRepoPath">工作目录。</param>
|
||||||
static void RunNuitkaBuild(LPCTSTR lpStartMessage, CString& strNuitkaCmd, const CString& strProductVersion, const CString& strFileVersion, LPCTSTR lpRepoPath)
|
static void RunNuitkaBuild(LPCTSTR lpStartMessage, CString& strNuitkaCmd, const CString& strProductVersion, const CString& strFileVersion, LPCTSTR lpRepoPath)
|
||||||
{
|
{
|
||||||
_tprintf(_T("%s\n"), lpStartMessage);
|
_tprintf(_T("%s\n"), lpStartMessage);
|
||||||
@@ -86,6 +103,20 @@ static void RunNuitkaBuild(LPCTSTR lpStartMessage, CString& strNuitkaCmd, const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// nuitkabuild / nuitkapydbuild 命令的公共实现:解析 pid、生成版本、调用 Nuitka。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="argc">参数个数。</param>
|
||||||
|
/// <param name="argv">参数数组。</param>
|
||||||
|
/// <param name="nCmdPrefixLen">argv[1] 中 "xxx=" 前缀长度。</param>
|
||||||
|
/// <param name="bBuildModule">TRUE 时使用 --module 模式。</param>
|
||||||
|
/// <param name="lpStartMessage">构建开始提示语。</param>
|
||||||
|
/// <param name="lpUsageName">命令名(用于错误提示)。</param>
|
||||||
|
/// <param name="nPidErrorCode">pid 无效时的退出码。</param>
|
||||||
|
/// <param name="nBidErrorCode">获取 bid 失败时的退出码。</param>
|
||||||
|
/// <param name="nTagErrorCode">获取 Tag 失败时的退出码。</param>
|
||||||
|
/// <param name="nCommitErrorCode">获取提交信息失败时的退出码。</param>
|
||||||
|
/// <returns>0 成功,否则为对应错误码。</returns>
|
||||||
static int HandleNuitkaBuildCommandCore(
|
static int HandleNuitkaBuildCommandCore(
|
||||||
int argc,
|
int argc,
|
||||||
TCHAR* argv[],
|
TCHAR* argv[],
|
||||||
@@ -165,6 +196,12 @@ static int HandleNuitkaBuildCommandCore(
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 处理 nuitkabuild=pid 命令:standalone 可执行文件打包。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="argc">参数个数。</param>
|
||||||
|
/// <param name="argv">参数数组。</param>
|
||||||
|
/// <returns>0 成功,17-21 等为各类错误码。</returns>
|
||||||
int HandleNuitkaBuildCommand(int argc, TCHAR* argv[])
|
int HandleNuitkaBuildCommand(int argc, TCHAR* argv[])
|
||||||
{
|
{
|
||||||
return HandleNuitkaBuildCommandCore(
|
return HandleNuitkaBuildCommandCore(
|
||||||
@@ -180,6 +217,12 @@ int HandleNuitkaBuildCommand(int argc, TCHAR* argv[])
|
|||||||
21);
|
21);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 处理 nuitkapydbuild=pid 命令:Python 扩展模块(.pyd)打包。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="argc">参数个数。</param>
|
||||||
|
/// <param name="argv">参数数组。</param>
|
||||||
|
/// <returns>0 成功,24-28 等为各类错误码。</returns>
|
||||||
int HandleNuitkaPydBuildCommand(int argc, TCHAR* argv[])
|
int HandleNuitkaPydBuildCommand(int argc, TCHAR* argv[])
|
||||||
{
|
{
|
||||||
return HandleNuitkaBuildCommandCore(
|
return HandleNuitkaBuildCommandCore(
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 处理 nuitkabuild=pid 命令:standalone 可执行文件打包。
|
||||||
|
/// </summary>
|
||||||
int HandleNuitkaBuildCommand(int argc, TCHAR* argv[]);
|
int HandleNuitkaBuildCommand(int argc, TCHAR* argv[]);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 处理 nuitkapydbuild=pid 命令:Python 扩展模块(.pyd)打包。
|
||||||
|
/// </summary>
|
||||||
int HandleNuitkaPydBuildCommand(int argc, TCHAR* argv[]);
|
int HandleNuitkaPydBuildCommand(int argc, TCHAR* argv[]);
|
||||||
|
|||||||
@@ -2,6 +2,13 @@
|
|||||||
#include "gitver.h"
|
#include "gitver.h"
|
||||||
#include "gitver_process.h"
|
#include "gitver_process.h"
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 按顺序将文件中多处旧字符串替换为新字符串并写回。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lpFile">目标文件路径。</param>
|
||||||
|
/// <param name="vtOldContent">待查找的旧内容列表。</param>
|
||||||
|
/// <param name="vtNewContent">对应的新内容列表。</param>
|
||||||
|
/// <returns>全部替换成功返回 TRUE。</returns>
|
||||||
BOOL ReplaceFileContent(LPCTSTR lpFile, std::vector<std::string>& vtOldContent, std::vector<std::string>& vtNewContent)
|
BOOL ReplaceFileContent(LPCTSTR lpFile, std::vector<std::string>& vtOldContent, std::vector<std::string>& vtNewContent)
|
||||||
{
|
{
|
||||||
if (vtOldContent.size() != vtNewContent.size())
|
if (vtOldContent.size() != vtNewContent.size())
|
||||||
@@ -69,9 +76,74 @@ BOOL ReplaceFileContent(LPCTSTR lpFile, std::vector<std::string>& vtOldContent,
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
CString StartProcess(LPCTSTR program, LPCTSTR args, LPCTSTR lpCurrentDirectory)
|
/// <summary>
|
||||||
|
/// 将子进程管道输出的原始字节按指定代码页解码为 CString。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="strRawOutput">管道原始输出字节。</param>
|
||||||
|
/// <param name="nOutputCodePage">解码代码页(CP_UTF8 或 CP_ACP 等)。</param>
|
||||||
|
/// <returns>解码后的字符串。</returns>
|
||||||
|
static CString DecodeProcessOutput(const std::string& strRawOutput, UINT nOutputCodePage)
|
||||||
{
|
{
|
||||||
CString strValue = _T("");
|
if (strRawOutput.empty())
|
||||||
|
{
|
||||||
|
return CString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nOutputCodePage == CP_UTF8)
|
||||||
|
{
|
||||||
|
int nWideSize = MultiByteToWideChar(CP_UTF8, 0, strRawOutput.data(), (int)strRawOutput.size(), NULL, 0);
|
||||||
|
if (nWideSize <= 0)
|
||||||
|
{
|
||||||
|
return CString();
|
||||||
|
}
|
||||||
|
|
||||||
|
CStringW strWide;
|
||||||
|
MultiByteToWideChar(CP_UTF8, 0, strRawOutput.data(), (int)strRawOutput.size(), strWide.GetBuffer(nWideSize), nWideSize);
|
||||||
|
strWide.ReleaseBuffer(nWideSize);
|
||||||
|
|
||||||
|
#ifdef UNICODE
|
||||||
|
return CString(strWide);
|
||||||
|
#else
|
||||||
|
int nGbkSize = WideCharToMultiByte(CP_ACP, 0, strWide, strWide.GetLength(), NULL, 0, NULL, NULL);
|
||||||
|
if (nGbkSize <= 0)
|
||||||
|
{
|
||||||
|
return CString();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string strGbk;
|
||||||
|
strGbk.resize(nGbkSize);
|
||||||
|
WideCharToMultiByte(CP_ACP, 0, strWide, strWide.GetLength(), &strGbk[0], nGbkSize, NULL, NULL);
|
||||||
|
return CString(strGbk.c_str(), (int)strGbk.size());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef UNICODE
|
||||||
|
int nSize = MultiByteToWideChar(nOutputCodePage, 0, strRawOutput.data(), (int)strRawOutput.size(), NULL, 0);
|
||||||
|
if (nSize <= 0)
|
||||||
|
{
|
||||||
|
return CString();
|
||||||
|
}
|
||||||
|
|
||||||
|
CString strValue;
|
||||||
|
MultiByteToWideChar(nOutputCodePage, 0, strRawOutput.data(), (int)strRawOutput.size(), strValue.GetBuffer(nSize), nSize);
|
||||||
|
strValue.ReleaseBuffer(nSize);
|
||||||
|
return strValue;
|
||||||
|
#else
|
||||||
|
return CString(strRawOutput.c_str(), (int)strRawOutput.size());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 启动子进程并捕获 stdout/stderr,按代码页解码后返回输出。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="program">可执行程序路径,NULL 时使用 args 中的命令。</param>
|
||||||
|
/// <param name="args">命令行参数。</param>
|
||||||
|
/// <param name="lpCurrentDirectory">工作目录。</param>
|
||||||
|
/// <param name="nOutputCodePage">输出解码代码页,默认 CP_ACP。</param>
|
||||||
|
/// <returns>子进程的标准输出/错误合并文本。</returns>
|
||||||
|
CString StartProcess(LPCTSTR program, LPCTSTR args, LPCTSTR lpCurrentDirectory, UINT nOutputCodePage)
|
||||||
|
{
|
||||||
|
std::string strRawOutput;
|
||||||
const int MY_PIPE_BUFFER_SIZE = 8912;
|
const int MY_PIPE_BUFFER_SIZE = 8912;
|
||||||
CString strMutableArgs = args == NULL ? _T("") : args;
|
CString strMutableArgs = args == NULL ? _T("") : args;
|
||||||
LPTSTR lpArgs = strMutableArgs.IsEmpty() ? NULL : strMutableArgs.GetBuffer();
|
LPTSTR lpArgs = strMutableArgs.IsEmpty() ? NULL : strMutableArgs.GetBuffer();
|
||||||
@@ -128,12 +200,7 @@ CString StartProcess(LPCTSTR program, LPCTSTR args, LPCTSTR lpCurrentDirectory)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
szPipeOut[dwStdLen] = '\0';
|
strRawOutput.append(szPipeOut, dwStdLen);
|
||||||
#ifdef UNICODE
|
|
||||||
strValue.AppendFormat(_T("%hs"), szPipeOut);
|
|
||||||
#else
|
|
||||||
strValue += szPipeOut;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WaitForSingleObject(processInfo.hProcess, INFINITE);
|
WaitForSingleObject(processInfo.hProcess, INFINITE);
|
||||||
@@ -160,6 +227,8 @@ CString StartProcess(LPCTSTR program, LPCTSTR args, LPCTSTR lpCurrentDirectory)
|
|||||||
strMutableArgs.ReleaseBuffer();
|
strMutableArgs.ReleaseBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CString strValue = DecodeProcessOutput(strRawOutput, nOutputCodePage);
|
||||||
|
|
||||||
_tprintf(_T("StartProcess program=%s args=%s cwd=%s output=%s\n"),
|
_tprintf(_T("StartProcess program=%s args=%s cwd=%s output=%s\n"),
|
||||||
program == NULL ? _T("<null>") : program,
|
program == NULL ? _T("<null>") : program,
|
||||||
args == NULL ? _T("<null>") : args,
|
args == NULL ? _T("<null>") : args,
|
||||||
|
|||||||
@@ -1,4 +1,13 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 按顺序将文件中多处旧字符串替换为新字符串并写回。
|
||||||
|
/// </summary>
|
||||||
BOOL ReplaceFileContent(LPCTSTR lpFile, std::vector<std::string>& vtOldContent, std::vector<std::string>& vtNewContent);
|
BOOL ReplaceFileContent(LPCTSTR lpFile, std::vector<std::string>& vtOldContent, std::vector<std::string>& vtNewContent);
|
||||||
CString StartProcess(LPCTSTR program, LPCTSTR args, LPCTSTR lpCurrentDirectory);
|
|
||||||
|
/// <summary>
|
||||||
|
/// 启动子进程并捕获 stdout/stderr。
|
||||||
|
/// nOutputCodePage 为管道原始字节的解码代码页,默认 CP_ACP(按系统 ANSI/GBK 直接使用);
|
||||||
|
/// CP_UTF8 时先按 UTF-8 解码为宽字符,MultiByte 工程再转为 GBK 存入 CString。
|
||||||
|
/// </summary>
|
||||||
|
CString StartProcess(LPCTSTR program, LPCTSTR args, LPCTSTR lpCurrentDirectory, UINT nOutputCodePage = CP_ACP);
|
||||||
|
|||||||
@@ -11,6 +11,15 @@ extern TCHAR g_szCurModuleDir[MAX_PATH];
|
|||||||
const UINT DEFAULT_MAJOR_WHEN_NO_TAG_FOR_PYINSTALLER = 1;
|
const UINT DEFAULT_MAJOR_WHEN_NO_TAG_FOR_PYINSTALLER = 1;
|
||||||
const UINT DEFAULT_MINOR_WHEN_NO_TAG_FOR_PYINSTALLER = 0;
|
const UINT DEFAULT_MINOR_WHEN_NO_TAG_FOR_PYINSTALLER = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 解析 pyinstaller 命令的 repodir=、params= 与 -test 参数。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="argc">参数个数。</param>
|
||||||
|
/// <param name="argv">参数数组。</param>
|
||||||
|
/// <param name="lpRepoPath">输出:仓库路径。</param>
|
||||||
|
/// <param name="strExtraArgs">输出:传给 PyInstaller 的参数。</param>
|
||||||
|
/// <param name="bTestMode">输出:是否测试模式。</param>
|
||||||
|
/// <returns>0 成功;3 未知/重复参数;23 无效 repodir。</returns>
|
||||||
static int ParsePyinstallerOptions(int argc, TCHAR* argv[], LPCTSTR& lpRepoPath, CString& strExtraArgs, BOOL& bTestMode)
|
static int ParsePyinstallerOptions(int argc, TCHAR* argv[], LPCTSTR& lpRepoPath, CString& strExtraArgs, BOOL& bTestMode)
|
||||||
{
|
{
|
||||||
lpRepoPath = NULL;
|
lpRepoPath = NULL;
|
||||||
@@ -71,7 +80,15 @@ static int ParsePyinstallerOptions(int argc, TCHAR* argv[], LPCTSTR& lpRepoPath,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 解析版本字符串 "a.b.c.d" → 四个分量
|
/// <summary>
|
||||||
|
/// 解析版本字符串 "a.b.c.d" 为四个无符号整数分量。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lpVer">版本字符串。</param>
|
||||||
|
/// <param name="v1">输出:第 1 段。</param>
|
||||||
|
/// <param name="v2">输出:第 2 段。</param>
|
||||||
|
/// <param name="v3">输出:第 3 段。</param>
|
||||||
|
/// <param name="v4">输出:第 4 段。</param>
|
||||||
|
/// <returns>格式正确返回 TRUE。</returns>
|
||||||
static BOOL ParseVersionTuple(LPCTSTR lpVer, UINT& v1, UINT& v2, UINT& v3, UINT& v4)
|
static BOOL ParseVersionTuple(LPCTSTR lpVer, UINT& v1, UINT& v2, UINT& v3, UINT& v4)
|
||||||
{
|
{
|
||||||
CString str(lpVer);
|
CString str(lpVer);
|
||||||
@@ -88,7 +105,11 @@ static BOOL ParseVersionTuple(LPCTSTR lpVer, UINT& v1, UINT& v2, UINT& v3, UINT&
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提取 params 中第一个空格分隔的 token(支持双引号括起的路径)
|
/// <summary>
|
||||||
|
/// 提取 params 字符串中第一个 token(支持双引号括起的路径)。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="strParams">完整 params 参数字符串。</param>
|
||||||
|
/// <returns>第一个 token,空输入返回空字符串。</returns>
|
||||||
static CString ExtractFirstToken(const CString& strParams)
|
static CString ExtractFirstToken(const CString& strParams)
|
||||||
{
|
{
|
||||||
CString str = strParams;
|
CString str = strParams;
|
||||||
@@ -105,7 +126,14 @@ static CString ExtractFirstToken(const CString& strParams)
|
|||||||
return str.Left(nSpace);
|
return str.Left(nSpace);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成 version_info.txt 到 lpDir 目录(PyInstaller VSVersionInfo 格式)
|
/// <summary>
|
||||||
|
/// 在指定目录生成 PyInstaller VSVersionInfo 格式的 version_info.txt。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lpDir">输出目录。</param>
|
||||||
|
/// <param name="lpProductVersion">产品版本号。</param>
|
||||||
|
/// <param name="lpFileVersion">文件版本号。</param>
|
||||||
|
/// <param name="strOutPath">输出:生成的文件完整路径。</param>
|
||||||
|
/// <returns>成功返回 TRUE。</returns>
|
||||||
static BOOL WriteVersionInfoFile(LPCTSTR lpDir, LPCTSTR lpProductVersion, LPCTSTR lpFileVersion, CString& strOutPath)
|
static BOOL WriteVersionInfoFile(LPCTSTR lpDir, LPCTSTR lpProductVersion, LPCTSTR lpFileVersion, CString& strOutPath)
|
||||||
{
|
{
|
||||||
UINT pv1 = 0, pv2 = 0, pv3 = 0, pv4 = 0;
|
UINT pv1 = 0, pv2 = 0, pv3 = 0, pv4 = 0;
|
||||||
@@ -165,8 +193,12 @@ static BOOL WriteVersionInfoFile(LPCTSTR lpDir, LPCTSTR lpProductVersion, LPCTST
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 向 .spec 文件注入或更新 version= 参数
|
/// <summary>
|
||||||
// 若已有 version= 则更新其值,否则在 name= 参数后新增一行
|
/// 向 .spec 文件注入或更新 version='version_info.txt' 参数。
|
||||||
|
/// 已有 version= 则更新整行,否则在 name= 行后插入。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lpSpecPath">.spec 文件路径。</param>
|
||||||
|
/// <returns>成功返回 TRUE。</returns>
|
||||||
static BOOL InjectVersionIntoSpec(LPCTSTR lpSpecPath)
|
static BOOL InjectVersionIntoSpec(LPCTSTR lpSpecPath)
|
||||||
{
|
{
|
||||||
if (!PathFileExists(lpSpecPath))
|
if (!PathFileExists(lpSpecPath))
|
||||||
@@ -260,8 +292,12 @@ static BOOL InjectVersionIntoSpec(LPCTSTR lpSpecPath)
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从 .spec 文件读取 version= 参数的值(单/双引号括起的路径)
|
/// <summary>
|
||||||
// 返回 FALSE 表示未找到、值为 None 或值为空
|
/// 从 .spec 文件读取 version= 参数引号内的外部版本信息文件路径。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lpSpecPath">.spec 文件路径。</param>
|
||||||
|
/// <param name="strVersionPath">输出:version= 指向的路径。</param>
|
||||||
|
/// <returns>找到有效引号路径返回 TRUE;None 或空值返回 FALSE。</returns>
|
||||||
static BOOL ReadVersionPathFromSpec(LPCTSTR lpSpecPath, CString& strVersionPath)
|
static BOOL ReadVersionPathFromSpec(LPCTSTR lpSpecPath, CString& strVersionPath)
|
||||||
{
|
{
|
||||||
strVersionPath.Empty();
|
strVersionPath.Empty();
|
||||||
@@ -310,7 +346,13 @@ static BOOL ReadVersionPathFromSpec(LPCTSTR lpSpecPath, CString& strVersionPath)
|
|||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新已有 version_info 文件的版本字段;若文件不存在则创建
|
/// <summary>
|
||||||
|
/// 更新已有 version_info 文件的版本字段;文件不存在时按模板创建。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lpPath">version_info.txt 或内联 spec 路径。</param>
|
||||||
|
/// <param name="lpProductVersion">产品版本号。</param>
|
||||||
|
/// <param name="lpFileVersion">文件版本号。</param>
|
||||||
|
/// <returns>成功返回 TRUE。</returns>
|
||||||
static BOOL UpdateVersionInfoFile(LPCTSTR lpPath, LPCTSTR lpProductVersion, LPCTSTR lpFileVersion)
|
static BOOL UpdateVersionInfoFile(LPCTSTR lpPath, LPCTSTR lpProductVersion, LPCTSTR lpFileVersion)
|
||||||
{
|
{
|
||||||
UINT pv1 = 0, pv2 = 0, pv3 = 0, pv4 = 0;
|
UINT pv1 = 0, pv2 = 0, pv3 = 0, pv4 = 0;
|
||||||
@@ -462,6 +504,139 @@ static BOOL UpdateVersionInfoFile(LPCTSTR lpPath, LPCTSTR lpProductVersion, LPCT
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 检查 .spec 中 version= 是否为无引号的 Python 标识符(内联 VSVersionInfo 对象名)。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lpSpecPath">.spec 文件路径。</param>
|
||||||
|
/// <returns>是内联版本块引用返回 TRUE。</returns>
|
||||||
|
static BOOL HasInlineVersionInfoBlock(LPCTSTR lpSpecPath)
|
||||||
|
{
|
||||||
|
if (!PathFileExists(lpSpecPath)) return FALSE;
|
||||||
|
|
||||||
|
CFile myFile;
|
||||||
|
CFileException fileEx;
|
||||||
|
if (!myFile.Open(lpSpecPath, CFile::modeRead, &fileEx)) return FALSE;
|
||||||
|
|
||||||
|
DWORD dwLen = (DWORD)myFile.GetLength();
|
||||||
|
std::vector<char> buf(dwLen + 1, 0);
|
||||||
|
myFile.Read(buf.data(), dwLen);
|
||||||
|
myFile.Close();
|
||||||
|
|
||||||
|
std::string strContent(buf.data(), dwLen);
|
||||||
|
std::string::size_type nPos = 0;
|
||||||
|
while ((nPos = strContent.find("version=", nPos)) != std::string::npos)
|
||||||
|
{
|
||||||
|
bool bIsParam = (nPos == 0
|
||||||
|
|| strContent[nPos - 1] == '\n'
|
||||||
|
|| strContent[nPos - 1] == '\r'
|
||||||
|
|| strContent[nPos - 1] == ' '
|
||||||
|
|| strContent[nPos - 1] == '\t');
|
||||||
|
if (bIsParam)
|
||||||
|
{
|
||||||
|
std::string::size_type nValStart = nPos + 8;
|
||||||
|
while (nValStart < strContent.size() && strContent[nValStart] == ' ')
|
||||||
|
nValStart++;
|
||||||
|
if (nValStart < strContent.size())
|
||||||
|
{
|
||||||
|
char c = strContent[nValStart];
|
||||||
|
// 无引号且以字母或下划线开头,视为 Python 标识符
|
||||||
|
if (c != '\'' && c != '"' && (isalpha((unsigned char)c) || c == '_'))
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
nPos += 8;
|
||||||
|
}
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 替换 .spec 文件中 version= 引号内的内联版本号字符串。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lpSpecPath">.spec 文件路径。</param>
|
||||||
|
/// <param name="lpNewValue">新版本号字符串。</param>
|
||||||
|
/// <returns>找到并替换返回 TRUE。</returns>
|
||||||
|
static BOOL UpdateVersionStringInSpec(LPCTSTR lpSpecPath, LPCTSTR lpNewValue)
|
||||||
|
{
|
||||||
|
if (!PathFileExists(lpSpecPath))
|
||||||
|
{
|
||||||
|
_tprintf(_T("错误: spec 文件不存在: %s\n"), lpSpecPath);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
CFile myFile;
|
||||||
|
CFileException fileEx;
|
||||||
|
if (!myFile.Open(lpSpecPath, CFile::modeReadWrite, &fileEx))
|
||||||
|
{
|
||||||
|
_tprintf(_T("错误: 无法打开 spec 文件: %s\n"), lpSpecPath);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD dwLen = (DWORD)myFile.GetLength();
|
||||||
|
std::vector<char> buf(dwLen + 1, 0);
|
||||||
|
myFile.Read(buf.data(), dwLen);
|
||||||
|
myFile.Close();
|
||||||
|
|
||||||
|
std::string strContent(buf.data(), dwLen);
|
||||||
|
CStringA strNewValueA(lpNewValue);
|
||||||
|
std::string strNewValue(strNewValueA);
|
||||||
|
|
||||||
|
std::string::size_type nPos = 0;
|
||||||
|
bool bUpdated = false;
|
||||||
|
while ((nPos = strContent.find("version=", nPos)) != std::string::npos)
|
||||||
|
{
|
||||||
|
bool bIsParam = (nPos == 0
|
||||||
|
|| strContent[nPos - 1] == '\n'
|
||||||
|
|| strContent[nPos - 1] == '\r'
|
||||||
|
|| strContent[nPos - 1] == ' '
|
||||||
|
|| strContent[nPos - 1] == '\t');
|
||||||
|
if (bIsParam)
|
||||||
|
{
|
||||||
|
std::string::size_type nValStart = nPos + 8; // skip "version="
|
||||||
|
while (nValStart < strContent.size() && strContent[nValStart] == ' ')
|
||||||
|
nValStart++;
|
||||||
|
if (nValStart < strContent.size())
|
||||||
|
{
|
||||||
|
char cQuote = strContent[nValStart];
|
||||||
|
if (cQuote == '\'' || cQuote == '"')
|
||||||
|
{
|
||||||
|
std::string::size_type nValEnd = strContent.find(cQuote, nValStart + 1);
|
||||||
|
if (nValEnd != std::string::npos)
|
||||||
|
{
|
||||||
|
strContent.replace(nValStart + 1, nValEnd - nValStart - 1, strNewValue);
|
||||||
|
bUpdated = true;
|
||||||
|
_tprintf(_T("成功: 已更新 spec 内联版本号 -> %s\n"), lpNewValue);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
nPos += 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bUpdated)
|
||||||
|
{
|
||||||
|
_tprintf(_T("错误: 未在 spec 文件中找到可替换的 version= 引号值。\n"));
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!myFile.Open(lpSpecPath, CFile::modeCreate | CFile::modeWrite, &fileEx))
|
||||||
|
{
|
||||||
|
_tprintf(_T("错误: 无法写入 spec 文件: %s\n"), lpSpecPath);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
myFile.Write(strContent.c_str(), (UINT)strContent.size());
|
||||||
|
myFile.Close();
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 处理 pyinstaller=pid 命令:生成版本、处理 spec/version_info、调用 PyInstaller 打包。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="argc">参数个数。</param>
|
||||||
|
/// <param name="argv">参数数组。</param>
|
||||||
|
/// <returns>0 成功;40-46 等为各类错误码。</returns>
|
||||||
int HandlePyinstallerBuildCommand(int argc, TCHAR* argv[])
|
int HandlePyinstallerBuildCommand(int argc, TCHAR* argv[])
|
||||||
{
|
{
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
@@ -535,13 +710,16 @@ int HandlePyinstallerBuildCommand(int argc, TCHAR* argv[])
|
|||||||
CString strExistingVersionPath;
|
CString strExistingVersionPath;
|
||||||
if (ReadVersionPathFromSpec(strSpecPath, strExistingVersionPath))
|
if (ReadVersionPathFromSpec(strSpecPath, strExistingVersionPath))
|
||||||
{
|
{
|
||||||
// spec 已有 version=,直接更新所指向的文件(不存在则创建)
|
// spec 已有 version=,解析其指向的文件路径
|
||||||
CString strVersionInfoFullPath;
|
CString strVersionInfoFullPath;
|
||||||
if (PathIsRelative(strExistingVersionPath))
|
if (PathIsRelative(strExistingVersionPath))
|
||||||
strVersionInfoFullPath.Format(_T("%s\\%s"), szSpecDir, strExistingVersionPath.GetString());
|
strVersionInfoFullPath.Format(_T("%s\\%s"), szSpecDir, strExistingVersionPath.GetString());
|
||||||
else
|
else
|
||||||
strVersionInfoFullPath = strExistingVersionPath;
|
strVersionInfoFullPath = strExistingVersionPath;
|
||||||
|
|
||||||
|
if (PathFileExists(strVersionInfoFullPath))
|
||||||
|
{
|
||||||
|
// 外部版本信息文件存在,直接更新
|
||||||
if (!UpdateVersionInfoFile(strVersionInfoFullPath, strProductVersion, strFileVersion))
|
if (!UpdateVersionInfoFile(strVersionInfoFullPath, strProductVersion, strFileVersion))
|
||||||
{
|
{
|
||||||
_tprintf(_T("错误: 无法更新版本信息文件: %s。\n"), strVersionInfoFullPath.GetString());
|
_tprintf(_T("错误: 无法更新版本信息文件: %s。\n"), strVersionInfoFullPath.GetString());
|
||||||
@@ -550,6 +728,27 @@ int HandlePyinstallerBuildCommand(int argc, TCHAR* argv[])
|
|||||||
_tprintf(_T("版本信息文件已更新: %s\n"), strVersionInfoFullPath.GetString());
|
_tprintf(_T("版本信息文件已更新: %s\n"), strVersionInfoFullPath.GetString());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
// version= 后的值不是有效的外部文件路径,视为 spec 内联版本号定义,直接修改 spec
|
||||||
|
_tprintf(_T("版本信息文件未找到(%s),将修改 spec 内联版本号。\n"), strVersionInfoFullPath.GetString());
|
||||||
|
if (!UpdateVersionStringInSpec(strSpecPath, strFileVersion))
|
||||||
|
{
|
||||||
|
_tprintf(_T("错误: 无法修改 spec 文件内联版本号。\n"));
|
||||||
|
return 46;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (HasInlineVersionInfoBlock(strSpecPath))
|
||||||
|
{
|
||||||
|
// spec 中 version=<标识符>,标识符指向 spec 内定义的 VSVersionInfo 对象,直接更新 spec 文件中的版本字段
|
||||||
|
if (!UpdateVersionInfoFile(strSpecPath, strProductVersion, strFileVersion))
|
||||||
|
{
|
||||||
|
_tprintf(_T("错误: 无法更新 spec 内联版本块: %s。\n"), strSpecPath.GetString());
|
||||||
|
return 45;
|
||||||
|
}
|
||||||
|
_tprintf(_T("spec 内联版本块已更新: %s\n"), strSpecPath.GetString());
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
// spec 未指定 version=,生成 version_info.txt 并注入 spec
|
// spec 未指定 version=,生成 version_info.txt 并注入 spec
|
||||||
CString strVersionInfoPath;
|
CString strVersionInfoPath;
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 处理 pyinstaller=pid 命令:生成版本信息并调用 PyInstaller 打包。
|
||||||
|
/// </summary>
|
||||||
int HandlePyinstallerBuildCommand(int argc, TCHAR* argv[]);
|
int HandlePyinstallerBuildCommand(int argc, TCHAR* argv[]);
|
||||||
|
|||||||
@@ -14,6 +14,12 @@ LPCTSTR GetProjectCodeTypeName(int nCodeType);
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 替换指定范围内包含指定标记的整行内容。
|
/// 替换指定范围内包含指定标记的整行内容。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="strContent">文件内容(原地修改)。</param>
|
||||||
|
/// <param name="strToken">行内查找标记。</param>
|
||||||
|
/// <param name="strNewLine">替换后的整行文本。</param>
|
||||||
|
/// <param name="nRangeStart">搜索范围起始偏移。</param>
|
||||||
|
/// <param name="nRangeEnd">搜索范围结束偏移。</param>
|
||||||
|
/// <returns>找到并替换返回 TRUE。</returns>
|
||||||
BOOL ReplaceWholeLineByTokenInRange(std::string& strContent, const std::string& strToken, const std::string& strNewLine, size_t nRangeStart, size_t nRangeEnd)
|
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)
|
if (nRangeStart == std::string::npos || nRangeEnd == std::string::npos || nRangeStart >= nRangeEnd)
|
||||||
@@ -54,11 +60,17 @@ BOOL ReplaceWholeLineByTokenInRange(std::string& strContent, const std::string&
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 在全文件范围内替换包含指定标记的整行。
|
||||||
|
/// </summary>
|
||||||
BOOL ReplaceWholeLineByToken(std::string& strContent, const std::string& strToken, const std::string& strNewLine)
|
BOOL ReplaceWholeLineByToken(std::string& strContent, const std::string& strToken, const std::string& strNewLine)
|
||||||
{
|
{
|
||||||
return ReplaceWholeLineByTokenInRange(strContent, strToken, strNewLine, 0, strContent.size());
|
return ReplaceWholeLineByTokenInRange(strContent, strToken, strNewLine, 0, strContent.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 替换首个匹配行,并删除后续所有含同一标记的重复行。
|
||||||
|
/// </summary>
|
||||||
BOOL EnsureSingleLineByToken(std::string& strContent, const std::string& strToken, const std::string& strNewLine)
|
BOOL EnsureSingleLineByToken(std::string& strContent, const std::string& strToken, const std::string& strNewLine)
|
||||||
{
|
{
|
||||||
std::string::size_type nPos = strContent.find(strToken);
|
std::string::size_type nPos = strContent.find(strToken);
|
||||||
@@ -127,6 +139,9 @@ BOOL EnsureSingleLineByToken(std::string& strContent, const std::string& strToke
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 若存在含标记的行则替换并去重,否则在文件末尾追加新行。
|
||||||
|
/// </summary>
|
||||||
BOOL EnsureSingleLineByTokenOrAppend(std::string& strContent, const std::string& strToken, const std::string& strNewLine)
|
BOOL EnsureSingleLineByTokenOrAppend(std::string& strContent, const std::string& strToken, const std::string& strNewLine)
|
||||||
{
|
{
|
||||||
if (strContent.find(strToken) != std::string::npos)
|
if (strContent.find(strToken) != std::string::npos)
|
||||||
@@ -147,6 +162,13 @@ BOOL EnsureSingleLineByTokenOrAppend(std::string& strContent, const std::string&
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 定位 .rc 文件中 VS_VERSION_INFO VERSIONINFO 块的 BEGIN…END 字节范围。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="strContent">.rc 文件内容。</param>
|
||||||
|
/// <param name="nBlockStart">输出:块起始偏移。</param>
|
||||||
|
/// <param name="nBlockEnd">输出:块结束偏移(不含末尾换行之后)。</param>
|
||||||
|
/// <returns>找到完整块返回 TRUE。</returns>
|
||||||
BOOL FindVersionInfoBlockRange(const std::string& strContent, size_t& nBlockStart, size_t& nBlockEnd)
|
BOOL FindVersionInfoBlockRange(const std::string& strContent, size_t& nBlockStart, size_t& nBlockEnd)
|
||||||
{
|
{
|
||||||
nBlockStart = std::string::npos;
|
nBlockStart = std::string::npos;
|
||||||
@@ -210,6 +232,12 @@ BOOL FindVersionInfoBlockRange(const std::string& strContent, size_t& nBlockStar
|
|||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 检测文本文件 BOM 类型;UTF-16 BOM 视为不支持。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="strData">文件原始字节。</param>
|
||||||
|
/// <param name="nBomLength">输出:BOM 长度(无 BOM 时为 0)。</param>
|
||||||
|
/// <returns>支持的编码返回 TRUE,UTF-16 返回 FALSE。</returns>
|
||||||
BOOL DetectSupportedTextBom(const std::string& strData, size_t& nBomLength)
|
BOOL DetectSupportedTextBom(const std::string& strData, size_t& nBomLength)
|
||||||
{
|
{
|
||||||
nBomLength = 0;
|
nBomLength = 0;
|
||||||
@@ -236,6 +264,13 @@ BOOL DetectSupportedTextBom(const std::string& strData, size_t& nBomLength)
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 以 ANSI/UTF-8(带 BOM 时保留前缀)读取文本文件。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lpFile">文件路径。</param>
|
||||||
|
/// <param name="strContent">输出:不含 BOM 的正文。</param>
|
||||||
|
/// <param name="strBomPrefix">输出:原始 BOM 前缀,写回时需保留。</param>
|
||||||
|
/// <returns>读取成功返回 TRUE。</returns>
|
||||||
BOOL ReadTextFileAsAnsi(LPCTSTR lpFile, std::string& strContent, std::string& strBomPrefix)
|
BOOL ReadTextFileAsAnsi(LPCTSTR lpFile, std::string& strContent, std::string& strBomPrefix)
|
||||||
{
|
{
|
||||||
strContent.clear();
|
strContent.clear();
|
||||||
@@ -284,6 +319,13 @@ BOOL ReadTextFileAsAnsi(LPCTSTR lpFile, std::string& strContent, std::string& st
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将文本内容与 BOM 前缀写回文件。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lpFile">文件路径。</param>
|
||||||
|
/// <param name="strContent">正文内容。</param>
|
||||||
|
/// <param name="strBomPrefix">BOM 前缀(可为空)。</param>
|
||||||
|
/// <returns>写入成功返回 TRUE。</returns>
|
||||||
BOOL WriteTextFileAsAnsi(LPCTSTR lpFile, const std::string& strContent, const std::string& strBomPrefix)
|
BOOL WriteTextFileAsAnsi(LPCTSTR lpFile, const std::string& strContent, const std::string& strBomPrefix)
|
||||||
{
|
{
|
||||||
CFile myFile;
|
CFile myFile;
|
||||||
@@ -303,6 +345,13 @@ BOOL WriteTextFileAsAnsi(LPCTSTR lpFile, const std::string& strContent, const st
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将 ProductVersion / FileVersion 写入 C++ .rc 文件的 VERSIONINFO 块。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="strRcFile">.rc 文件路径。</param>
|
||||||
|
/// <param name="strProductVersion">产品版本号。</param>
|
||||||
|
/// <param name="strFileVersion">文件版本号。</param>
|
||||||
|
/// <returns>成功返回 TRUE。</returns>
|
||||||
BOOL RewriteSetVerToCppRc(const CString& strRcFile, const CString& strProductVersion, const CString& strFileVersion)
|
BOOL RewriteSetVerToCppRc(const CString& strRcFile, const CString& strProductVersion, const CString& strFileVersion)
|
||||||
{
|
{
|
||||||
std::string strContent;
|
std::string strContent;
|
||||||
@@ -356,6 +405,13 @@ BOOL RewriteSetVerToCppRc(const CString& strRcFile, const CString& strProductVer
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将 ProductVersion / FileVersion 写入 C# AssemblyInfo.cs。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="strAssemblyInfoFile">AssemblyInfo.cs 路径。</param>
|
||||||
|
/// <param name="strProductVersion">产品版本号(AssemblyVersion)。</param>
|
||||||
|
/// <param name="strFileVersion">文件版本号(AssemblyFileVersion)。</param>
|
||||||
|
/// <returns>成功返回 TRUE。</returns>
|
||||||
BOOL RewriteSetVerToAssemblyInfo(const CString& strAssemblyInfoFile, const CString& strProductVersion, const CString& strFileVersion)
|
BOOL RewriteSetVerToAssemblyInfo(const CString& strAssemblyInfoFile, const CString& strProductVersion, const CString& strFileVersion)
|
||||||
{
|
{
|
||||||
std::string strContent;
|
std::string strContent;
|
||||||
@@ -392,6 +448,13 @@ BOOL RewriteSetVerToAssemblyInfo(const CString& strAssemblyInfoFile, const CStri
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 按项目类型(C++ / C#)将版本号写入对应资源文件。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lpRepoPath">项目根目录,NULL 时使用当前模块目录。</param>
|
||||||
|
/// <param name="strProductVersion">产品版本号。</param>
|
||||||
|
/// <param name="strFileVersion">文件版本号。</param>
|
||||||
|
/// <returns>0 成功;15 不支持的项目类型;35 写版本失败。</returns>
|
||||||
int RewriteSetVerByProjectType(LPCTSTR lpRepoPath, const CString& strProductVersion, const CString& strFileVersion)
|
int RewriteSetVerByProjectType(LPCTSTR lpRepoPath, const CString& strProductVersion, const CString& strFileVersion)
|
||||||
{
|
{
|
||||||
LPCTSTR lpBaseDir = lpRepoPath == NULL ? g_szCurModuleDir : lpRepoPath;
|
LPCTSTR lpBaseDir = lpRepoPath == NULL ? g_szCurModuleDir : lpRepoPath;
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 按项目类型(C++ / C#)将版本号写入对应资源文件。
|
||||||
|
/// </summary>
|
||||||
int RewriteSetVerByProjectType(LPCTSTR lpRepoPath, const CString& strProductVersion, const CString& strFileVersion);
|
int RewriteSetVerByProjectType(LPCTSTR lpRepoPath, const CString& strProductVersion, const CString& strFileVersion);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "gitver_cli.h"
|
#include "gitver_cli.h"
|
||||||
#include "gitver_version.h"
|
#include "gitver_version.h"
|
||||||
#include "gitver_process.h"
|
#include "gitver_process.h"
|
||||||
|
#include "gitver_upload.h"
|
||||||
|
|
||||||
extern TCHAR g_szCurModuleDir[MAX_PATH];
|
extern TCHAR g_szCurModuleDir[MAX_PATH];
|
||||||
|
|
||||||
@@ -15,6 +16,9 @@ BOOL WriteTextFileAsAnsi(LPCTSTR lpFile, const std::string& strContent, const st
|
|||||||
|
|
||||||
void PrintInvalidRepoPathError(LPCTSTR lpRepoPath);
|
void PrintInvalidRepoPathError(LPCTSTR lpRepoPath);
|
||||||
int ParseSetVerOptions(int argc, TCHAR* argv[], int nStartIndex, LPCTSTR& lpRepoPath, BOOL& bTestMode);
|
int ParseSetVerOptions(int argc, TCHAR* argv[], int nStartIndex, LPCTSTR& lpRepoPath, BOOL& bTestMode);
|
||||||
|
BOOL FindPreferredCppRcFileAt(LPCTSTR lpBaseDir, CString& strResFile);
|
||||||
|
BOOL FindPreferredAssemblyInfoFileAt(LPCTSTR lpBaseDir, CString& strResFile);
|
||||||
|
BOOL FindFirstFileByPatternRecursive(LPCTSTR lpBaseDir, LPCTSTR lpPattern, CString& strFoundPath);
|
||||||
|
|
||||||
static const UINT DEFAULT_MAJOR_WHEN_NO_TAG_FOR_SETUP = 1;
|
static const UINT DEFAULT_MAJOR_WHEN_NO_TAG_FOR_SETUP = 1;
|
||||||
static const UINT DEFAULT_MINOR_WHEN_NO_TAG_FOR_SETUP = 0;
|
static const UINT DEFAULT_MINOR_WHEN_NO_TAG_FOR_SETUP = 0;
|
||||||
@@ -27,9 +31,7 @@ static const UINT DEFAULT_MINOR_WHEN_NO_TAG_FOR_SETUP = 0;
|
|||||||
/// 在字符串内容中查找首个包含 strToken 的整行,
|
/// 在字符串内容中查找首个包含 strToken 的整行,
|
||||||
/// 用 strNewLine 替换该行(不含行尾换行符)。
|
/// 用 strNewLine 替换该行(不含行尾换行符)。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
static BOOL ReplaceLineByToken(std::string& strContent,
|
static BOOL ReplaceLineByToken(std::string& strContent, const std::string& strToken, const std::string& strNewLine)
|
||||||
const std::string& strToken,
|
|
||||||
const std::string& strNewLine)
|
|
||||||
{
|
{
|
||||||
std::string::size_type nPos = strContent.find(strToken);
|
std::string::size_type nPos = strContent.find(strToken);
|
||||||
if (nPos == std::string::npos)
|
if (nPos == std::string::npos)
|
||||||
@@ -246,15 +248,269 @@ static CString FindNsisCompiler()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────────────────────────
|
// ─────────────────────────────────────────────
|
||||||
// 公共执行函数(供 HandleSetupCommand 和 setver -setup=N 共用)
|
// 从项目文件读取版本号(当 setup=n 未指定 pid 时使用)
|
||||||
|
// ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从 strContent 中查找 strToken 之后第一个双引号括起来的值。
|
||||||
|
/// </summary>
|
||||||
|
static CString ExtractQuotedValueAfterToken(const std::string& strContent, const std::string& strToken)
|
||||||
|
{
|
||||||
|
std::string::size_type nPos = strContent.find(strToken);
|
||||||
|
if (nPos == std::string::npos)
|
||||||
|
return CString();
|
||||||
|
|
||||||
|
std::string::size_type nStart = strContent.find('"', nPos + strToken.size());
|
||||||
|
if (nStart == std::string::npos)
|
||||||
|
return CString();
|
||||||
|
++nStart;
|
||||||
|
|
||||||
|
std::string::size_type nEnd = strContent.find('"', nStart);
|
||||||
|
if (nEnd == std::string::npos)
|
||||||
|
return CString();
|
||||||
|
|
||||||
|
return CString(strContent.substr(nStart, nEnd - nStart).c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从 .rc / AssemblyInfo.cs / version_info.txt / .spec 文件中读取版本号字符串。
|
||||||
|
/// 优先顺序:.rc > AssemblyInfo.cs > version_info.txt/.spec。
|
||||||
|
/// </summary>
|
||||||
|
static BOOL ReadVersionFromProjectFiles(LPCTSTR lpBaseDir, CString& strVersion)
|
||||||
|
{
|
||||||
|
strVersion.Empty();
|
||||||
|
std::string strContent, strBomPrefix;
|
||||||
|
|
||||||
|
// 1. .rc 文件 -> VALUE "FileVersion", "x.x.x.x"
|
||||||
|
CString strRcFile;
|
||||||
|
if (FindPreferredCppRcFileAt(lpBaseDir, strRcFile))
|
||||||
|
{
|
||||||
|
if (ReadTextFileAsAnsi(strRcFile, strContent, strBomPrefix))
|
||||||
|
{
|
||||||
|
CString strVal = ExtractQuotedValueAfterToken(strContent, "VALUE \"FileVersion\",");
|
||||||
|
if (!strVal.IsEmpty())
|
||||||
|
{
|
||||||
|
strVersion = strVal;
|
||||||
|
_tprintf(_T("从 .rc 文件读取版本号: %s\n"), strVersion.GetString());
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. AssemblyInfo.cs -> AssemblyFileVersion("x.x.x.x")
|
||||||
|
CString strAiFile;
|
||||||
|
if (FindPreferredAssemblyInfoFileAt(lpBaseDir, strAiFile))
|
||||||
|
{
|
||||||
|
if (ReadTextFileAsAnsi(strAiFile, strContent, strBomPrefix))
|
||||||
|
{
|
||||||
|
CString strVal = ExtractQuotedValueAfterToken(strContent, "AssemblyFileVersion(");
|
||||||
|
if (!strVal.IsEmpty())
|
||||||
|
{
|
||||||
|
strVersion = strVal;
|
||||||
|
_tprintf(_T("从 AssemblyInfo.cs 读取版本号: %s\n"), strVersion.GetString());
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. version_info.txt / .spec -> filevers=(x, x, x, x)
|
||||||
|
CString strSpecFile;
|
||||||
|
CString strBaseWithSlash = lpBaseDir;
|
||||||
|
if (!strBaseWithSlash.IsEmpty() && strBaseWithSlash.Right(1) != _T("\\"))
|
||||||
|
strBaseWithSlash += _T("\\");
|
||||||
|
if (FindFirstFileByPatternRecursive(strBaseWithSlash, _T("version_info.txt"), strSpecFile) ||
|
||||||
|
FindFirstFileByPatternRecursive(strBaseWithSlash, _T("*.spec"), strSpecFile))
|
||||||
|
{
|
||||||
|
if (ReadTextFileAsAnsi(strSpecFile, strContent, strBomPrefix))
|
||||||
|
{
|
||||||
|
std::string::size_type nPos = strContent.find("filevers=(");
|
||||||
|
if (nPos != std::string::npos)
|
||||||
|
{
|
||||||
|
nPos += 10;
|
||||||
|
std::string::size_type nEnd = strContent.find(')', nPos);
|
||||||
|
if (nEnd != std::string::npos)
|
||||||
|
{
|
||||||
|
std::string strTuple = strContent.substr(nPos, nEnd - nPos);
|
||||||
|
std::string strClean;
|
||||||
|
for (char c : strTuple)
|
||||||
|
if (c != ' ') strClean += (c == ',' ? '.' : c);
|
||||||
|
strVersion = CString(strClean.c_str());
|
||||||
|
_tprintf(_T("从 version_info.txt/.spec 读取版本号: %s\n"), strVersion.GetString());
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────
|
||||||
|
// 解析编译输出中的安装包路径
|
||||||
|
// ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从文本中提取首个双引号括起来的路径;若无引号则返回原文本。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="strText">待解析的文本行。</param>
|
||||||
|
/// <returns>提取出的路径字符串。</returns>
|
||||||
|
static CString ExtractQuotedPath(const CString& strText)
|
||||||
|
{
|
||||||
|
int nStart = strText.Find(_T('"'));
|
||||||
|
if (nStart < 0)
|
||||||
|
{
|
||||||
|
return strText;
|
||||||
|
}
|
||||||
|
|
||||||
|
int nEnd = strText.Find(_T('"'), nStart + 1);
|
||||||
|
if (nEnd <= nStart)
|
||||||
|
{
|
||||||
|
return strText;
|
||||||
|
}
|
||||||
|
|
||||||
|
return strText.Mid(nStart + 1, nEnd - nStart - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 在指定目录中查找修改时间最新的 .exe 文件。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lpDir">要搜索的目录路径。</param>
|
||||||
|
/// <param name="strExePath">输出:找到的最新 exe 完整路径。</param>
|
||||||
|
/// <returns>找到则返回 TRUE,否则返回 FALSE。</returns>
|
||||||
|
static BOOL FindNewestExeInDirectory(LPCTSTR lpDir, CString& strExePath)
|
||||||
|
{
|
||||||
|
strExePath.Empty();
|
||||||
|
if (lpDir == NULL || lpDir[0] == _T('\0') || !PathIsDirectory(lpDir))
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
CString strPattern;
|
||||||
|
strPattern.Format(_T("%s\\*.exe"), lpDir);
|
||||||
|
|
||||||
|
WIN32_FIND_DATA findData = { 0 };
|
||||||
|
HANDLE hFind = ::FindFirstFile(strPattern, &findData);
|
||||||
|
if (hFind == INVALID_HANDLE_VALUE)
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
FILETIME ftNewest = { 0 };
|
||||||
|
BOOL bFound = FALSE;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bFound || CompareFileTime(&findData.ftLastWriteTime, &ftNewest) > 0)
|
||||||
|
{
|
||||||
|
ftNewest = findData.ftLastWriteTime;
|
||||||
|
strExePath.Format(_T("%s\\%s"), lpDir, findData.cFileName);
|
||||||
|
bFound = TRUE;
|
||||||
|
}
|
||||||
|
} while (::FindNextFile(hFind, &findData));
|
||||||
|
|
||||||
|
::FindClose(hFind);
|
||||||
|
return bFound && PathFileExists(strExePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从 Inno Setup / NSIS 编译输出中解析安装包路径。
|
||||||
|
/// 优先匹配编译器输出的路径行,失败则回退到 Output 目录或脚本目录中最新的 exe。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="nSetupType">0=Inno Setup,1=NSIS。</param>
|
||||||
|
/// <param name="strOutput">编译器标准输出。</param>
|
||||||
|
/// <param name="strScriptDir">安装脚本所在目录。</param>
|
||||||
|
/// <param name="strInstallerPath">输出:解析到的安装包路径。</param>
|
||||||
|
/// <returns>成功解析到有效路径则返回 TRUE。</returns>
|
||||||
|
static BOOL ParseInstallerPathFromBuildOutput(int nSetupType, const CString& strOutput, const CString& strScriptDir, CString& strInstallerPath)
|
||||||
|
{
|
||||||
|
strInstallerPath.Empty();
|
||||||
|
|
||||||
|
if (nSetupType == 0)
|
||||||
|
{
|
||||||
|
LPCTSTR lpMarker = _T("Resulting Setup program filename is:");
|
||||||
|
int nPos = strOutput.Find(lpMarker);
|
||||||
|
if (nPos >= 0)
|
||||||
|
{
|
||||||
|
CString strRest = strOutput.Mid(nPos + (int)_tcslen(lpMarker));
|
||||||
|
strRest.Trim();
|
||||||
|
|
||||||
|
int nLineBreak = strRest.Find(_T('\n'));
|
||||||
|
CString strFirst = (nLineBreak >= 0) ? strRest.Left(nLineBreak) : strRest;
|
||||||
|
strFirst.Trim();
|
||||||
|
if (!strFirst.IsEmpty())
|
||||||
|
{
|
||||||
|
strFirst = ExtractQuotedPath(strFirst);
|
||||||
|
if (PathFileExists(strFirst))
|
||||||
|
{
|
||||||
|
strInstallerPath = strFirst;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nLineBreak >= 0)
|
||||||
|
{
|
||||||
|
CString strSecond = strRest.Mid(nLineBreak + 1);
|
||||||
|
strSecond.Trim();
|
||||||
|
strSecond = ExtractQuotedPath(strSecond);
|
||||||
|
if (PathFileExists(strSecond))
|
||||||
|
{
|
||||||
|
strInstallerPath = strSecond;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int nPos = 0;
|
||||||
|
while ((nPos = strOutput.Find(_T("Output:"), nPos)) >= 0)
|
||||||
|
{
|
||||||
|
CString strRest = strOutput.Mid(nPos + 7);
|
||||||
|
int nLineBreak = strRest.Find(_T('\n'));
|
||||||
|
CString strLine = (nLineBreak >= 0) ? strRest.Left(nLineBreak) : strRest;
|
||||||
|
strLine.Trim();
|
||||||
|
strLine = ExtractQuotedPath(strLine);
|
||||||
|
if (!strLine.IsEmpty() && PathFileExists(strLine))
|
||||||
|
{
|
||||||
|
strInstallerPath = strLine;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
nPos += 7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CString strOutputDir;
|
||||||
|
strOutputDir.Format(_T("%s\\Output"), strScriptDir.GetString());
|
||||||
|
if (FindNewestExeInDirectory(strOutputDir, strInstallerPath))
|
||||||
|
{
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return FindNewestExeInDirectory(strScriptDir, strInstallerPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────
|
||||||
|
// 公共执行函数
|
||||||
// 不回写版本信息到项目文件,只修改安装脚本并调用编译器打包。
|
// 不回写版本信息到项目文件,只修改安装脚本并调用编译器打包。
|
||||||
// ─────────────────────────────────────────────
|
// ─────────────────────────────────────────────
|
||||||
|
|
||||||
BOOL ExecuteSetupBuild(int nSetupType, const CString& strProductVersion, const CString& strFileVersion)
|
/// <summary>
|
||||||
|
/// 修改安装脚本版本号并调用 Inno Setup / NSIS 编译器打包。
|
||||||
|
/// 不写回 .rc / AssemblyInfo.cs 等项目文件。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="nSetupType">0=Inno Setup (.iss),1=NSIS (.nsi)。</param>
|
||||||
|
/// <param name="strProductVersion">产品版本号(仅日志用途)。</param>
|
||||||
|
/// <param name="strFileVersion">写入脚本的文件版本号。</param>
|
||||||
|
/// <param name="strInstallerPath">输出:编译生成的安装包路径(可能为空)。</param>
|
||||||
|
/// <returns>编译流程执行成功返回 TRUE,脚本/编译器缺失或修改失败返回 FALSE。</returns>
|
||||||
|
BOOL ExecuteSetupBuild(int nSetupType, const CString& strProductVersion, const CString& strFileVersion, CString& strInstallerPath)
|
||||||
{
|
{
|
||||||
#ifdef _DEBUG
|
strInstallerPath.Empty();
|
||||||
Sleep(1500);
|
|
||||||
#endif
|
|
||||||
// 查找安装脚本
|
// 查找安装脚本
|
||||||
CString strScriptPath;
|
CString strScriptPath;
|
||||||
if (!FindSetupScript(nSetupType, strScriptPath))
|
if (!FindSetupScript(nSetupType, strScriptPath))
|
||||||
@@ -301,9 +557,7 @@ BOOL ExecuteSetupBuild(int nSetupType, const CString& strProductVersion, const C
|
|||||||
|
|
||||||
// 构建并执行编译命令
|
// 构建并执行编译命令
|
||||||
CString strCmd;
|
CString strCmd;
|
||||||
strCmd.Format(_T("cmd /c %s %s"),
|
strCmd.Format(_T("cmd /c %s %s"), QuoteCmdArg(strCompiler).GetString(), QuoteCmdArg(strScriptPath).GetString());
|
||||||
QuoteCmdArg(strCompiler).GetString(),
|
|
||||||
QuoteCmdArg(strScriptPath).GetString());
|
|
||||||
_tprintf(_T("执行编译: %s\n"), strCmd.GetString());
|
_tprintf(_T("执行编译: %s\n"), strCmd.GetString());
|
||||||
|
|
||||||
CString strOutput = StartProcess(NULL, strCmd.GetBuffer(), strScriptDir.GetString());
|
CString strOutput = StartProcess(NULL, strCmd.GetBuffer(), strScriptDir.GetString());
|
||||||
@@ -313,56 +567,102 @@ BOOL ExecuteSetupBuild(int nSetupType, const CString& strProductVersion, const C
|
|||||||
{
|
{
|
||||||
_tprintf(_T("警告: 编译器无输出,可能执行失败。\n"));
|
_tprintf(_T("警告: 编译器无输出,可能执行失败。\n"));
|
||||||
}
|
}
|
||||||
|
else if (!ParseInstallerPathFromBuildOutput(nSetupType, strOutput, strScriptDir, strInstallerPath))
|
||||||
|
{
|
||||||
|
_tprintf(_T("警告: 未能从编译输出中识别安装包路径。\n"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_tprintf(_T("安装包路径: %s\n"), strInstallerPath.GetString());
|
||||||
|
}
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────────────────────────
|
// ─────────────────────────────────────────────
|
||||||
// 独立命令入口(argv[1] 形如 "-setup=0")
|
// 独立命令入口(argv[1] 形如 "setup=0")
|
||||||
// ─────────────────────────────────────────────
|
// ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 处理 setup=n 命令:解析参数、确定版本号、编译安装包并更新 upload_info.txt。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="argc">参数个数。</param>
|
||||||
|
/// <param name="argv">参数数组,argv[1] 形如 setup=0 或 setup=1。</param>
|
||||||
|
/// <returns>0 成功;36 setup 值无效;38 脚本修改/编译失败;其他见 setver 相关退出码。</returns>
|
||||||
int HandleSetupCommand(int argc, TCHAR* argv[])
|
int HandleSetupCommand(int argc, TCHAR* argv[])
|
||||||
{
|
{
|
||||||
#ifdef _DEBUG
|
// 解析 setup=n 中的 n
|
||||||
Sleep(15000);
|
|
||||||
#endif
|
|
||||||
// 解析 -setup=n 中的 n
|
|
||||||
CString strSetupArg = argv[1];
|
CString strSetupArg = argv[1];
|
||||||
CString strN = strSetupArg.Mid(7); // 跳过 "-setup="
|
CString strN = strSetupArg.Mid(6); // 跳过 "setup="
|
||||||
UINT nSetupType = UINT(-1);
|
UINT nSetupType = UINT(-1);
|
||||||
if (!TryParseUInt16(strN, nSetupType) || (nSetupType != 0 && nSetupType != 1))
|
if (!TryParseUInt16(strN, nSetupType) || (nSetupType != 0 && nSetupType != 1))
|
||||||
{
|
{
|
||||||
_tprintf(_T("错误: -setup 参数值 \"%s\" 不支持,仅支持 0(Inno Setup)或 1(NSIS)。\n"),
|
_tprintf(_T("错误: setup 参数值 \"%s\" 不支持,仅支持 0(Inno Setup)或 1(NSIS)。\n"),
|
||||||
strN.GetString());
|
strN.GetString());
|
||||||
return 36;
|
return 36;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argc < 3)
|
// 解析可选的 pid=m、repodir=、-test
|
||||||
|
LPCTSTR lpRepoPath = NULL;
|
||||||
|
BOOL bTestMode = FALSE;
|
||||||
|
BOOL bHasPid = FALSE;
|
||||||
|
UINT nPid = 0;
|
||||||
|
|
||||||
|
for (int i = 2; i < argc; ++i)
|
||||||
{
|
{
|
||||||
_tprintf(_T("错误: 参数不足。\n"));
|
CString strArg = argv[i];
|
||||||
_tprintf(_T("用法:gitver -setup=0|1 [pid] [repodir=<path>可选]\n"));
|
if (strArg.CompareNoCase(_T("-test")) == 0)
|
||||||
_tprintf(_T("示例:gitver -setup=0 5\n"));
|
{
|
||||||
_tprintf(_T("示例:gitver -setup=1 5 repodir=E:\\Code\\MyProj\n"));
|
bTestMode = TRUE;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (strArg.GetLength() > 8 && strArg.Left(8).CompareNoCase(_T("repodir=")) == 0)
|
||||||
|
{
|
||||||
|
if (lpRepoPath != NULL)
|
||||||
|
{
|
||||||
|
_tprintf(_T("错误: repodir 参数重复: %s\n"), argv[i]);
|
||||||
|
return 23;
|
||||||
|
}
|
||||||
|
LPCTSTR lpPath = argv[i] + 8;
|
||||||
|
if (!PathIsDirectory(lpPath))
|
||||||
|
{
|
||||||
|
PrintInvalidRepoPathError(lpPath);
|
||||||
|
return 23;
|
||||||
|
}
|
||||||
|
lpRepoPath = lpPath;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (strArg.GetLength() > 4 && strArg.Left(4).CompareNoCase(_T("pid=")) == 0)
|
||||||
|
{
|
||||||
|
if (bHasPid)
|
||||||
|
{
|
||||||
|
_tprintf(_T("错误: pid 参数重复: %s\n"), argv[i]);
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
CString strPid = strArg.Mid(4);
|
||||||
|
if (!TryParseUInt16(strPid, nPid))
|
||||||
|
{
|
||||||
|
_tprintf(_T("错误: pid 参数值 \"%s\" 无效,应为 0-65535 范围整数。\n"), strPid.GetString());
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
bHasPid = TRUE;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_tprintf(_T("错误: 多余的参数 %s\n"), argv[i]);
|
||||||
return 3;
|
return 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
UINT nPid = 0;
|
if (lpRepoPath == NULL)
|
||||||
int nArgRet = ParseUInt16ArgOrError(argv, 2, _T("pid"), nPid, 4);
|
|
||||||
if (nArgRet != 0)
|
|
||||||
{
|
{
|
||||||
return nArgRet;
|
lpRepoPath = g_szCurModuleDir;
|
||||||
}
|
|
||||||
|
|
||||||
LPCTSTR lpRepoPath = NULL;
|
|
||||||
BOOL bTestMode = FALSE;
|
|
||||||
int nRepoArgRet = ParseSetVerOptions(argc, argv, 3, lpRepoPath, bTestMode);
|
|
||||||
if (nRepoArgRet != 0)
|
|
||||||
{
|
|
||||||
return nRepoArgRet;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CString strFileVersion;
|
CString strFileVersion;
|
||||||
CString strProductVersion;
|
CString strProductVersion;
|
||||||
|
|
||||||
|
if (bHasPid)
|
||||||
|
{
|
||||||
|
// 从 git 仓库生成版本号
|
||||||
VersionBuildErrorCodes errorCodes = { 5, 6, 9 };
|
VersionBuildErrorCodes errorCodes = { 5, 6, 9 };
|
||||||
int nVersionRet = BuildVersionsFromRepo(
|
int nVersionRet = BuildVersionsFromRepo(
|
||||||
lpRepoPath,
|
lpRepoPath,
|
||||||
@@ -372,7 +672,6 @@ int HandleSetupCommand(int argc, TCHAR* argv[])
|
|||||||
strFileVersion,
|
strFileVersion,
|
||||||
DEFAULT_MAJOR_WHEN_NO_TAG_FOR_SETUP,
|
DEFAULT_MAJOR_WHEN_NO_TAG_FOR_SETUP,
|
||||||
DEFAULT_MINOR_WHEN_NO_TAG_FOR_SETUP);
|
DEFAULT_MINOR_WHEN_NO_TAG_FOR_SETUP);
|
||||||
|
|
||||||
if (nVersionRet != 0)
|
if (nVersionRet != 0)
|
||||||
{
|
{
|
||||||
return nVersionRet;
|
return nVersionRet;
|
||||||
@@ -391,12 +690,36 @@ int HandleSetupCommand(int argc, TCHAR* argv[])
|
|||||||
|
|
||||||
_tprintf(_T("FileVersion=%s\n"), strFileVersion.GetString());
|
_tprintf(_T("FileVersion=%s\n"), strFileVersion.GetString());
|
||||||
_tprintf(_T("ProductVersion=%s\n"), strProductVersion.GetString());
|
_tprintf(_T("ProductVersion=%s\n"), strProductVersion.GetString());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 从项目文件读取版本号
|
||||||
|
if (!ReadVersionFromProjectFiles(lpRepoPath, strFileVersion))
|
||||||
|
{
|
||||||
|
_tprintf(_T("错误: 未找到 .rc、AssemblyInfo.cs 或 version_info.txt/.spec 文件,无法读取版本号。\n"));
|
||||||
|
_tprintf(_T("请使用 pid=m 参数指定产品 ID 以从 git 仓库生成版本号。\n"));
|
||||||
|
return 35;
|
||||||
|
}
|
||||||
|
strProductVersion = strFileVersion;
|
||||||
|
}
|
||||||
|
|
||||||
// 不回写版本信息到项目文件,直接修改安装脚本并编译打包
|
// 修改安装脚本并编译打包
|
||||||
if (!ExecuteSetupBuild((int)nSetupType, strProductVersion, strFileVersion))
|
CString strInstallerPath;
|
||||||
|
if (!ExecuteSetupBuild((int)nSetupType, strProductVersion, strFileVersion, strInstallerPath))
|
||||||
{
|
{
|
||||||
return 38;
|
return 38;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!strInstallerPath.IsEmpty())
|
||||||
|
{
|
||||||
|
TCHAR szInfoPath[MAX_PATH] = { 0 };
|
||||||
|
_tcscpy_s(szInfoPath, lpRepoPath);
|
||||||
|
PathAppend(szInfoPath, _T("upload_info.txt"));
|
||||||
|
if (!WriteUploadInfoPackageFile(szInfoPath, strInstallerPath))
|
||||||
|
{
|
||||||
|
_tprintf(_T("警告: 未能写入 upload_info.txt\n"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找安装脚本、修改版本号并调用编译器打包。
|
/// 查找安装脚本、修改版本号并调用编译器打包。
|
||||||
/// nSetupType=0: Inno Setup (.iss);nSetupType=1: NSIS (.nsi)。
|
/// nSetupType=0: Inno Setup (.iss);nSetupType=1: NSIS (.nsi)。
|
||||||
/// 不写回项目版本文件(.rc / AssemblyInfo.cs)。
|
/// 不写回项目版本文件(.rc / AssemblyInfo.cs)。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>TRUE 成功;FALSE 失败(错误已打印)。</returns>
|
/// <returns>TRUE 成功;FALSE 失败(错误已打印)。</returns>
|
||||||
BOOL ExecuteSetupBuild(int nSetupType, const CString& strProductVersion, const CString& strFileVersion);
|
BOOL ExecuteSetupBuild(int nSetupType, const CString& strProductVersion, const CString& strFileVersion, CString& strInstallerPath);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 处理独立的 -setup=n 命令行入口。
|
/// 处理独立的 setup=n 命令行入口。
|
||||||
/// 退出码:0 成功,36 -setup 值不支持,37 未找到脚本,
|
/// 退出码:0 成功,36 setup 值不支持,37 未找到脚本,
|
||||||
/// 38 修改脚本失败,39 未找到编译器,其他同通用错误码。
|
/// 38 修改脚本失败,39 未找到编译器,其他同通用错误码。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
int HandleSetupCommand(int argc, TCHAR* argv[]);
|
int HandleSetupCommand(int argc, TCHAR* argv[]);
|
||||||
|
|||||||
@@ -10,6 +10,14 @@ extern TCHAR g_szCurModuleDir[MAX_PATH];
|
|||||||
const UINT DEFAULT_MAJOR_WHEN_NO_TAG_FOR_TAG = 1;
|
const UINT DEFAULT_MAJOR_WHEN_NO_TAG_FOR_TAG = 1;
|
||||||
const UINT DEFAULT_MINOR_WHEN_NO_TAG_FOR_TAG = 0;
|
const UINT DEFAULT_MINOR_WHEN_NO_TAG_FOR_TAG = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取当前分支下最近 nMaxCount 个符合 major.minor 格式的 Tag。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lpRepoPath">仓库根目录。</param>
|
||||||
|
/// <param name="strBranch">输出:当前分支名。</param>
|
||||||
|
/// <param name="vtTags">输出:Tag 列表(按版本升序,取最后 nMaxCount 个)。</param>
|
||||||
|
/// <param name="nMaxCount">最多返回的 Tag 数量。</param>
|
||||||
|
/// <returns>成功返回 TRUE(无 Tag 时 vtTags 为空亦视为成功)。</returns>
|
||||||
BOOL GetRecentMajorMinorTags(LPCTSTR lpRepoPath, CString& strBranch, std::vector<BranchTagInfo>& vtTags, int nMaxCount)
|
BOOL GetRecentMajorMinorTags(LPCTSTR lpRepoPath, CString& strBranch, std::vector<BranchTagInfo>& vtTags, int nMaxCount)
|
||||||
{
|
{
|
||||||
vtTags.clear();
|
vtTags.clear();
|
||||||
@@ -73,6 +81,10 @@ BOOL GetRecentMajorMinorTags(LPCTSTR lpRepoPath, CString& strBranch, std::vector
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 交互式 Tag 创建流程:展示最近 Tag、选择 major+1 或 minor+1、创建并推送。
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>0 成功;30-34 交互/创建/验证失败;36 推送失败。</returns>
|
||||||
int HandleCreateTagInteractive()
|
int HandleCreateTagInteractive()
|
||||||
{
|
{
|
||||||
CString strBranch;
|
CString strBranch;
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 分支 Tag 的 major/minor 解析结果。
|
||||||
|
/// </summary>
|
||||||
struct BranchTagInfo
|
struct BranchTagInfo
|
||||||
{
|
{
|
||||||
CString strTag;
|
CString strTag;
|
||||||
@@ -7,5 +10,12 @@ struct BranchTagInfo
|
|||||||
UINT nMinor;
|
UINT nMinor;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取当前分支下最近 nMaxCount 个符合 major.minor 格式的 Tag。
|
||||||
|
/// </summary>
|
||||||
BOOL GetRecentMajorMinorTags(LPCTSTR lpRepoPath, CString& strBranch, std::vector<BranchTagInfo>& vtTags, int nMaxCount);
|
BOOL GetRecentMajorMinorTags(LPCTSTR lpRepoPath, CString& strBranch, std::vector<BranchTagInfo>& vtTags, int nMaxCount);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 交互式 Tag 创建流程:展示最近 Tag、选择递增方式、创建并推送。
|
||||||
|
/// </summary>
|
||||||
int HandleCreateTagInteractive();
|
int HandleCreateTagInteractive();
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 项目源码类型枚举。
|
||||||
|
/// </summary>
|
||||||
enum ProjectCodeType
|
enum ProjectCodeType
|
||||||
{
|
{
|
||||||
PROJECT_UNKNOWN = 0,
|
PROJECT_UNKNOWN = 0,
|
||||||
|
|||||||
1238
GitVer/GitVer_upload.cpp
Normal file
1238
GitVer/GitVer_upload.cpp
Normal file
File diff suppressed because it is too large
Load Diff
22
GitVer/GitVer_upload.h
Normal file
22
GitVer/GitVer_upload.h
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 处理 upload 命令:登录 PMC 并上传包文件。
|
||||||
|
/// 退出码:0 成功,47 缺少参数,48 文件不存在,49 info 无效或未设置同名环境变量密码,
|
||||||
|
/// 50 登录失败,51 上传 HTTP 失败,52 验证未找到版本,53 网络错误,54 Tag/分支校验失败。
|
||||||
|
/// </summary>
|
||||||
|
int HandleUploadCommand(int argc, TCHAR* argv[]);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 更新 upload_info.txt 中的 product_version、file_version、file_change_log,保留其他字段。
|
||||||
|
/// </summary>
|
||||||
|
BOOL WriteUploadInfoVersions(
|
||||||
|
LPCTSTR lpInfoFile,
|
||||||
|
LPCTSTR lpProductVersion,
|
||||||
|
LPCTSTR lpFileVersion,
|
||||||
|
LPCTSTR lpFileChangeLog);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 更新 upload_info.txt 中的 file(安装包路径),保留其他字段。
|
||||||
|
/// </summary>
|
||||||
|
BOOL WriteUploadInfoPackageFile(LPCTSTR lpInfoFile, LPCTSTR lpPackageFile);
|
||||||
@@ -4,6 +4,33 @@
|
|||||||
#include "gitver_process.h"
|
#include "gitver_process.h"
|
||||||
#include "gitver_version.h"
|
#include "gitver_version.h"
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 检测 Git 仓库是否处于正常分支状态(非 HEAD 游离)。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lpRepoPath">仓库根目录。</param>
|
||||||
|
/// <param name="strStatus">输出:symbolic-ref HEAD 结果。</param>
|
||||||
|
/// <returns>在分支上返回 TRUE,游离状态返回 FALSE。</returns>
|
||||||
|
BOOL TryGetGitStatus(LPCTSTR lpRepoPath, CString& strStatus)
|
||||||
|
{
|
||||||
|
strStatus = StartProcess(NULL, _T("cmd /c git symbolic-ref --quiet HEAD"), lpRepoPath);
|
||||||
|
strStatus.Replace(_T("\r"), _T(""));
|
||||||
|
strStatus.Trim();
|
||||||
|
|
||||||
|
if (strStatus.IsEmpty())
|
||||||
|
{// 游离状态
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 正常状态;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取当前检出的本地分支名称。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lpRepoPath">仓库根目录。</param>
|
||||||
|
/// <param name="strBranch">输出:分支名。</param>
|
||||||
|
/// <returns>成功返回 TRUE。</returns>
|
||||||
BOOL TryGetCurrentBranch(LPCTSTR lpRepoPath, CString& strBranch)
|
BOOL TryGetCurrentBranch(LPCTSTR lpRepoPath, CString& strBranch)
|
||||||
{
|
{
|
||||||
_tprintf(_T("获取当前分支名称: \n"));
|
_tprintf(_T("获取当前分支名称: \n"));
|
||||||
@@ -20,6 +47,59 @@ BOOL TryGetCurrentBranch(LPCTSTR lpRepoPath, CString& strBranch)
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 在 HEAD 游离时,通过远程分支推断当前所属分支名(origin/ 前缀已去除)。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lpRepoPath">仓库根目录。</param>
|
||||||
|
/// <param name="strBranch">输出:推断的分支名。</param>
|
||||||
|
/// <returns>找到远程分支返回 TRUE。</returns>
|
||||||
|
BOOL TryGetDetectedBranch(LPCTSTR lpRepoPath, CString& strBranch)
|
||||||
|
{
|
||||||
|
// 远程仓库名必须以 origin 开头;
|
||||||
|
// "C:\Program Files\Git\bin\bash.exe" -c "git branch -r --contains HEAD | head -n1 | sed 's/.*origin\///'"
|
||||||
|
strBranch = StartProcess(NULL, _T("\"C:\\Program Files\\Git\\bin\\bash.exe\" -c \"git branch -r --contains HEAD | head -n1 | sed 's/.*origin\\///'\""), lpRepoPath);
|
||||||
|
//strBranch = StartProcess("C:\\Program Files\\Git\\bin\\bash.exe", _T("git branch -r --contains HEAD | head -n1 | sed 's/.*origin\\///'"), lpRepoPath);
|
||||||
|
strBranch.Replace(_T("\r"), _T(""));
|
||||||
|
strBranch.Replace(_T("\n"), _T(""));
|
||||||
|
strBranch.Trim();
|
||||||
|
|
||||||
|
if (strBranch.IsEmpty())
|
||||||
|
{
|
||||||
|
// 没有找到远程分支;
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取指向当前 HEAD 的 Tag 名称。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lpRepoPath">仓库根目录。</param>
|
||||||
|
/// <param name="strTag">输出:Tag 名(多个时取 git 输出首行)。</param>
|
||||||
|
/// <returns>存在 Tag 返回 TRUE。</returns>
|
||||||
|
BOOL TryGetDetectedTag(LPCTSTR lpRepoPath, CString& strTag)
|
||||||
|
{
|
||||||
|
strTag = StartProcess(NULL, _T("cmd /c git tag --points-at HEAD"), lpRepoPath);
|
||||||
|
strTag.Replace(_T("\r"), _T(""));
|
||||||
|
strTag.Replace(_T("\n"), _T(""));
|
||||||
|
strTag.Trim();
|
||||||
|
|
||||||
|
if (strTag.IsEmpty())
|
||||||
|
{
|
||||||
|
// 没有找到标签;
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从分支名解析 bid(分支 ID):main/master=0,release=1,dev=2,其余取末段数字。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="strBranch">分支名称。</param>
|
||||||
|
/// <param name="nBid">输出:分支 bid。</param>
|
||||||
|
/// <returns>解析成功返回 TRUE。</returns>
|
||||||
BOOL TryGetBidFromBranch(const CString& strBranch, UINT& nBid)
|
BOOL TryGetBidFromBranch(const CString& strBranch, UINT& nBid)
|
||||||
{
|
{
|
||||||
nBid = 0;
|
nBid = 0;
|
||||||
@@ -34,6 +114,18 @@ BOOL TryGetBidFromBranch(const CString& strBranch, UINT& nBid)
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (strBranch.CompareNoCase(_T("release")) == 0)
|
||||||
|
{
|
||||||
|
nBid = 1;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strBranch.CompareNoCase(_T("dev")) == 0)
|
||||||
|
{
|
||||||
|
nBid = 2;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
int nDotPos = strBranch.ReverseFind(_T('.'));
|
int nDotPos = strBranch.ReverseFind(_T('.'));
|
||||||
if (nDotPos <= 0 || nDotPos >= strBranch.GetLength() - 1)
|
if (nDotPos <= 0 || nDotPos >= strBranch.GetLength() - 1)
|
||||||
{
|
{
|
||||||
@@ -44,6 +136,14 @@ BOOL TryGetBidFromBranch(const CString& strBranch, UINT& nBid)
|
|||||||
return TryParseUInt16(strNum, nBid);
|
return TryParseUInt16(strNum, nBid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 解析符合「分支名.major.minor」格式的 Tag,提取 major 与 minor。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="strBranch">分支名称。</param>
|
||||||
|
/// <param name="strTag">Tag 名称。</param>
|
||||||
|
/// <param name="nMajor">输出:主版本号。</param>
|
||||||
|
/// <param name="nMinor">输出:次版本号。</param>
|
||||||
|
/// <returns>格式匹配且解析成功返回 TRUE。</returns>
|
||||||
BOOL TryParseTagMajorMinor(const CString& strBranch, const CString& strTag, UINT& nMajor, UINT& nMinor)
|
BOOL TryParseTagMajorMinor(const CString& strBranch, const CString& strTag, UINT& nMajor, UINT& nMinor)
|
||||||
{
|
{
|
||||||
if (strBranch.IsEmpty())
|
if (strBranch.IsEmpty())
|
||||||
@@ -79,6 +179,15 @@ BOOL TryParseTagMajorMinor(const CString& strBranch, const CString& strTag, UINT
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取当前分支下版本号最大的 major.minor Tag(按 v:refname 排序遍历)。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lpRepoPath">仓库根目录。</param>
|
||||||
|
/// <param name="strBranch">分支名称。</param>
|
||||||
|
/// <param name="strTag">输出:最新 Tag 全名。</param>
|
||||||
|
/// <param name="nMajor">输出:主版本号。</param>
|
||||||
|
/// <param name="nMinor">输出:次版本号。</param>
|
||||||
|
/// <returns>找到匹配 Tag 返回 TRUE。</returns>
|
||||||
BOOL GetLastMajorMinorTag(LPCTSTR lpRepoPath, const CString& strBranch, CString& strTag, UINT& nMajor, UINT& nMinor)
|
BOOL GetLastMajorMinorTag(LPCTSTR lpRepoPath, const CString& strBranch, CString& strTag, UINT& nMajor, UINT& nMinor)
|
||||||
{
|
{
|
||||||
strTag.Empty();
|
strTag.Empty();
|
||||||
@@ -122,6 +231,13 @@ BOOL GetLastMajorMinorTag(LPCTSTR lpRepoPath, const CString& strBranch, CString&
|
|||||||
return !strTag.IsEmpty();
|
return !strTag.IsEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将 SYSTEMTIME 转换为 FileVersion 中的 yy 与 mmdd 分量。
|
||||||
|
/// 月份 1-12 映射为 11-22(mmdd = (month+10)*100 + day)。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stLocal">本地时间。</param>
|
||||||
|
/// <param name="nYY">输出:年份后两位。</param>
|
||||||
|
/// <param name="nMMDD">输出:编码后的月日。</param>
|
||||||
void GetFileVersionDateFromSystemTime(const SYSTEMTIME& stLocal, UINT& nYY, UINT& nMMDD)
|
void GetFileVersionDateFromSystemTime(const SYSTEMTIME& stLocal, UINT& nYY, UINT& nMMDD)
|
||||||
{
|
{
|
||||||
nYY = stLocal.wYear % 100;
|
nYY = stLocal.wYear % 100;
|
||||||
@@ -129,6 +245,14 @@ void GetFileVersionDateFromSystemTime(const SYSTEMTIME& stLocal, UINT& nYY, UINT
|
|||||||
nMMDD = (stLocal.wMonth + 10) * 100 + stLocal.wDay;
|
nMMDD = (stLocal.wMonth + 10) * 100 + stLocal.wDay;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 统计指定日期内 origin/分支 的提交次数(用于 FileVersion 末段 id)。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lpRepoPath">仓库根目录。</param>
|
||||||
|
/// <param name="strBranch">分支名称。</param>
|
||||||
|
/// <param name="stDate">目标日期。</param>
|
||||||
|
/// <param name="nId">输出:当日提交数。</param>
|
||||||
|
/// <returns>成功返回 TRUE。</returns>
|
||||||
BOOL TryGetBranchCommitCountByDate(LPCTSTR lpRepoPath, const CString& strBranch, const SYSTEMTIME& stDate, UINT& nId)
|
BOOL TryGetBranchCommitCountByDate(LPCTSTR lpRepoPath, const CString& strBranch, const SYSTEMTIME& stDate, UINT& nId)
|
||||||
{
|
{
|
||||||
nId = 0;
|
nId = 0;
|
||||||
@@ -142,7 +266,7 @@ BOOL TryGetBranchCommitCountByDate(LPCTSTR lpRepoPath, const CString& strBranch,
|
|||||||
|
|
||||||
CString strCmd;
|
CString strCmd;
|
||||||
strCmd.Format(
|
strCmd.Format(
|
||||||
_T("cmd /c git rev-list --count --since=\"%s 00:00:00\" --until=\"%s 23:59:59\" \"%s\""),
|
_T("cmd /c git rev-list --count --since=\"%s 00:00:00\" --until=\"%s 23:59:59\" \"origin/%s\""),
|
||||||
strDate.GetString(),
|
strDate.GetString(),
|
||||||
strDate.GetString(),
|
strDate.GetString(),
|
||||||
strBranch.GetString());
|
strBranch.GetString());
|
||||||
@@ -167,6 +291,13 @@ BOOL TryGetBranchCommitCountByDate(LPCTSTR lpRepoPath, const CString& strBranch,
|
|||||||
return TryParseUInt16(strCount, nId);
|
return TryParseUInt16(strCount, nId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取分支最近一次提交的日期(git log -1 --date=short)。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lpRepoPath">仓库根目录。</param>
|
||||||
|
/// <param name="strBranch">分支名称。</param>
|
||||||
|
/// <param name="stDate">输出:提交日期。</param>
|
||||||
|
/// <returns>成功返回 TRUE。</returns>
|
||||||
BOOL TryGetLastBranchCommitDate(LPCTSTR lpRepoPath, const CString& strBranch, SYSTEMTIME& stDate)
|
BOOL TryGetLastBranchCommitDate(LPCTSTR lpRepoPath, const CString& strBranch, SYSTEMTIME& stDate)
|
||||||
{
|
{
|
||||||
::ZeroMemory(&stDate, sizeof(stDate));
|
::ZeroMemory(&stDate, sizeof(stDate));
|
||||||
@@ -209,7 +340,123 @@ BOOL TryGetLastBranchCommitDate(LPCTSTR lpRepoPath, const CString& strBranch, SY
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOL ResolveFileVersionDateAndCommitCount(LPCTSTR lpRepoPath, const CString& strBranch, UINT& nYY, UINT& nMMDD, UINT& nId)
|
/// <summary>
|
||||||
|
/// 获取 Tag 对应提交的日期(git log -1 --date=short)。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lpRepoPath">仓库根目录。</param>
|
||||||
|
/// <param name="strTag">Tag 名称。</param>
|
||||||
|
/// <param name="stDate">输出:提交日期。</param>
|
||||||
|
/// <returns>成功返回 TRUE。</returns>
|
||||||
|
BOOL TryGetTagCommitDate(LPCTSTR lpRepoPath, const CString& strTag, SYSTEMTIME& stDate)
|
||||||
|
{
|
||||||
|
::ZeroMemory(&stDate, sizeof(stDate));
|
||||||
|
if (strTag.IsEmpty())
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
CString strCmd;
|
||||||
|
strCmd.Format(_T("cmd /c git log -1 --date=short --format=%%cd \"%s\""), strTag.GetString());
|
||||||
|
|
||||||
|
CString strDate = StartProcess(NULL, strCmd.GetBuffer(), lpRepoPath);
|
||||||
|
strCmd.ReleaseBuffer();
|
||||||
|
|
||||||
|
if (strDate.IsEmpty())
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
strDate.Replace(_T("\r"), _T(""));
|
||||||
|
strDate.Replace(_T("\n"), _T(""));
|
||||||
|
strDate.Trim();
|
||||||
|
|
||||||
|
if (strDate.IsEmpty() || strDate.Left(6).CompareNoCase(_T("fatal:")) == 0)
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int nYear = 0;
|
||||||
|
unsigned int nMonth = 0;
|
||||||
|
unsigned int nDay = 0;
|
||||||
|
if (_stscanf_s(strDate.GetString(), _T("%u-%u-%u"), &nYear, &nMonth, &nDay) != 3)
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
stDate.wYear = static_cast<WORD>(nYear);
|
||||||
|
stDate.wMonth = static_cast<WORD>(nMonth);
|
||||||
|
stDate.wDay = static_cast<WORD>(nDay);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 基于 Tag 提交日期解析 FileVersion 的 yy、mmdd 与 id(当日提交数)。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lpRepoPath">仓库根目录。</param>
|
||||||
|
/// <param name="strBranch">分支名称。</param>
|
||||||
|
/// <param name="strTag">Tag 名称。</param>
|
||||||
|
/// <param name="nYY">输出:年份后两位。</param>
|
||||||
|
/// <param name="nMMDD">输出:编码月日。</param>
|
||||||
|
/// <param name="nId">输出:提交序号。</param>
|
||||||
|
/// <param name="pOutDate">可选输出:Tag 提交日期。</param>
|
||||||
|
/// <returns>成功返回 TRUE。</returns>
|
||||||
|
BOOL ResolveTagFileVersionDateAndCommitCount(LPCTSTR lpRepoPath, const CString& strBranch, const CString& strTag, UINT& nYY, UINT& nMMDD, UINT& nId, SYSTEMTIME* pOutDate)
|
||||||
|
{
|
||||||
|
nYY = 0;
|
||||||
|
nMMDD = 0;
|
||||||
|
nId = 0;
|
||||||
|
|
||||||
|
if (strTag.IsEmpty())
|
||||||
|
{
|
||||||
|
_tprintf(_T("错误: 标签名称为空\n"));
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
SYSTEMTIME stTargetDate = { 0 };
|
||||||
|
if (!TryGetTagCommitDate(lpRepoPath, strTag, stTargetDate))
|
||||||
|
{
|
||||||
|
_tprintf(_T("错误: 获取标签 [%s] 对应提交日期失败\n"), strTag.GetString());
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
UINT nCommitCount = 0;
|
||||||
|
if (!TryGetBranchCommitCountByDate(lpRepoPath, strBranch, stTargetDate, nCommitCount))
|
||||||
|
{
|
||||||
|
_tprintf(_T("错误: 获取 [%s] 在 [%04u-%02u-%02u] 的提交数量失败\n"),
|
||||||
|
strBranch.GetString(),
|
||||||
|
stTargetDate.wYear,
|
||||||
|
stTargetDate.wMonth,
|
||||||
|
stTargetDate.wDay);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
_tprintf(_T("获取标签提交信息成功: %04u-%02u-%02u, 提交数量: %u\n"),
|
||||||
|
stTargetDate.wYear,
|
||||||
|
stTargetDate.wMonth,
|
||||||
|
stTargetDate.wDay,
|
||||||
|
nCommitCount);
|
||||||
|
|
||||||
|
GetFileVersionDateFromSystemTime(stTargetDate, nYY, nMMDD);
|
||||||
|
nId = nCommitCount;
|
||||||
|
if (pOutDate != NULL)
|
||||||
|
{
|
||||||
|
*pOutDate = stTargetDate;
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 基于当前日期(或最近提交日)解析 FileVersion 的 yy、mmdd 与 id。
|
||||||
|
/// 当天提交数为 0 时回退到最近一次提交日期。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lpRepoPath">仓库根目录。</param>
|
||||||
|
/// <param name="strBranch">分支名称。</param>
|
||||||
|
/// <param name="nYY">输出:年份后两位。</param>
|
||||||
|
/// <param name="nMMDD">输出:编码月日。</param>
|
||||||
|
/// <param name="nId">输出:提交序号。</param>
|
||||||
|
/// <param name="pOutDate">可选输出:采用的日期。</param>
|
||||||
|
/// <returns>成功返回 TRUE。</returns>
|
||||||
|
BOOL ResolveFileVersionDateAndCommitCount(LPCTSTR lpRepoPath, const CString& strBranch, UINT& nYY, UINT& nMMDD, UINT& nId, SYSTEMTIME* pOutDate)
|
||||||
{
|
{
|
||||||
nYY = 0;
|
nYY = 0;
|
||||||
nMMDD = 0;
|
nMMDD = 0;
|
||||||
@@ -251,9 +498,73 @@ BOOL ResolveFileVersionDateAndCommitCount(LPCTSTR lpRepoPath, const CString& str
|
|||||||
|
|
||||||
GetFileVersionDateFromSystemTime(stTargetDate, nYY, nMMDD);
|
GetFileVersionDateFromSystemTime(stTargetDate, nYY, nMMDD);
|
||||||
nId = nTodayCommitCount;
|
nId = nTodayCommitCount;
|
||||||
|
if (pOutDate != NULL)
|
||||||
|
{
|
||||||
|
*pOutDate = stTargetDate;
|
||||||
|
}
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取指定日期内 origin/分支 的提交摘要列表(UTF-8 输出,一行一条)。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lpRepoPath">仓库根目录。</param>
|
||||||
|
/// <param name="strBranch">分支名称。</param>
|
||||||
|
/// <param name="stDate">目标日期。</param>
|
||||||
|
/// <param name="strLog">输出:提交摘要,多行以换行分隔。</param>
|
||||||
|
/// <returns>成功返回 TRUE。</returns>
|
||||||
|
BOOL TryGetGitLogByBranchDate(LPCTSTR lpRepoPath, const CString& strBranch, const SYSTEMTIME& stDate, CString& strLog)
|
||||||
|
{
|
||||||
|
strLog.Empty();
|
||||||
|
if (strBranch.IsEmpty())
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
CString strCmd;
|
||||||
|
strCmd.Format(
|
||||||
|
_T("cmd /c git log --reverse --pretty=format:%%s --since=\"%04u-%02u-%02u 00:00:00\" --until=\"%04u-%02u-%02u 23:59:59\" \"origin/%s\""),
|
||||||
|
stDate.wYear,
|
||||||
|
stDate.wMonth,
|
||||||
|
stDate.wDay,
|
||||||
|
stDate.wYear,
|
||||||
|
stDate.wMonth,
|
||||||
|
stDate.wDay,
|
||||||
|
strBranch.GetString());
|
||||||
|
|
||||||
|
CString strOutput = StartProcess(NULL, strCmd.GetBuffer(), lpRepoPath, CP_UTF8);
|
||||||
|
strCmd.ReleaseBuffer();
|
||||||
|
if (strOutput.IsEmpty())
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
strOutput.Replace(_T("\r"), _T(""));
|
||||||
|
strOutput.Trim();
|
||||||
|
if (strOutput.IsEmpty() || strOutput.Left(6).CompareNoCase(_T("fatal:")) == 0)
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
strLog = strOutput;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从 Git 仓库信息构建 ProductVersion 与 FileVersion 字符串。
|
||||||
|
/// 正常分支:ProductVersion = pid.bid.major.minor,FileVersion = pid.yy.mmdd.id。
|
||||||
|
/// HEAD 游离时改用远程分支与 Tag 解析。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lpRepoPath">仓库根目录。</param>
|
||||||
|
/// <param name="nPid">产品 ID。</param>
|
||||||
|
/// <param name="errorCodes">各步骤失败时的退出码映射。</param>
|
||||||
|
/// <param name="strProductVersion">输出:产品版本号。</param>
|
||||||
|
/// <param name="strFileVersion">输出:文件版本号。</param>
|
||||||
|
/// <param name="nDefaultMajor">无 Tag 时的默认 major。</param>
|
||||||
|
/// <param name="nDefaultMinor">无 Tag 时的默认 minor。</param>
|
||||||
|
/// <param name="pOutBranch">可选输出:使用的分支名。</param>
|
||||||
|
/// <param name="pOutFileVersionDate">可选输出:FileVersion 采用的日期。</param>
|
||||||
|
/// <returns>0 成功,否则返回 errorCodes 中对应错误码。</returns>
|
||||||
int BuildVersionsFromRepo(
|
int BuildVersionsFromRepo(
|
||||||
LPCTSTR lpRepoPath,
|
LPCTSTR lpRepoPath,
|
||||||
UINT nPid,
|
UINT nPid,
|
||||||
@@ -261,7 +572,9 @@ int BuildVersionsFromRepo(
|
|||||||
CString& strProductVersion,
|
CString& strProductVersion,
|
||||||
CString& strFileVersion,
|
CString& strFileVersion,
|
||||||
UINT nDefaultMajor,
|
UINT nDefaultMajor,
|
||||||
UINT nDefaultMinor)
|
UINT nDefaultMinor,
|
||||||
|
CString* pOutBranch,
|
||||||
|
SYSTEMTIME* pOutFileVersionDate)
|
||||||
{
|
{
|
||||||
_tprintf(_T("开始构建版本: 仓库路径=%s pid=%u 默认Tag(%u.%u)\n"),
|
_tprintf(_T("开始构建版本: 仓库路径=%s pid=%u 默认Tag(%u.%u)\n"),
|
||||||
lpRepoPath == NULL ? _T("<null>") : lpRepoPath,
|
lpRepoPath == NULL ? _T("<null>") : lpRepoPath,
|
||||||
@@ -270,7 +583,47 @@ int BuildVersionsFromRepo(
|
|||||||
nDefaultMinor);
|
nDefaultMinor);
|
||||||
|
|
||||||
UINT nBid = 0;
|
UINT nBid = 0;
|
||||||
|
CString strStatus;
|
||||||
CString strBranch;
|
CString strBranch;
|
||||||
|
UINT nMajor = 0, nMinor = 0;
|
||||||
|
UINT nYY = 0, nMMDD = 0, nId = 0;
|
||||||
|
if (!TryGetGitStatus(lpRepoPath, strStatus))
|
||||||
|
{
|
||||||
|
_tprintf(_T("警告: 当前处于 HEAD 游离状态\n"));
|
||||||
|
if (!TryGetDetectedBranch(lpRepoPath, strBranch))
|
||||||
|
{
|
||||||
|
_tprintf(_T("错误: 获取远程分支失败\n"));
|
||||||
|
return errorCodes.nStatusErrorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
CString strTag;
|
||||||
|
if (!TryGetDetectedTag(lpRepoPath, strTag))
|
||||||
|
{
|
||||||
|
_tprintf(_T("错误: 获取远程分支标签失败\n"));
|
||||||
|
return errorCodes.nStatusErrorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处于游离状态,使用远程分支和标签来解析版本号;
|
||||||
|
if (TryParseTagMajorMinor(strBranch, strTag, nMajor, nMinor))
|
||||||
|
{
|
||||||
|
_tprintf(_T("游离标签版本: %s (major=%u minor=%u)\n"), strTag.GetString(), nMajor, nMinor);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ResolveTagFileVersionDateAndCommitCount(lpRepoPath, strBranch, strTag, nYY, nMMDD, nId, pOutFileVersionDate))
|
||||||
|
{
|
||||||
|
_tprintf(_T("错误: 获取游离标签提交信息失败\n"));
|
||||||
|
return errorCodes.nCommitErrorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pOutBranch != NULL)
|
||||||
|
{
|
||||||
|
*pOutBranch = strBranch;
|
||||||
|
}
|
||||||
|
|
||||||
|
_tprintf(_T("游离标签提交信息: yy=%u mmdd=%u id=%u\n"), nYY, nMMDD, nId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
if (!TryGetCurrentBranch(lpRepoPath, strBranch))
|
if (!TryGetCurrentBranch(lpRepoPath, strBranch))
|
||||||
{
|
{
|
||||||
_tprintf(_T("错误: 获取当前分支失败\n"));
|
_tprintf(_T("错误: 获取当前分支失败\n"));
|
||||||
@@ -286,8 +639,6 @@ int BuildVersionsFromRepo(
|
|||||||
_tprintf(_T("分支 bid: %u\n"), nBid);
|
_tprintf(_T("分支 bid: %u\n"), nBid);
|
||||||
|
|
||||||
CString strLastTag;
|
CString strLastTag;
|
||||||
UINT nMajor = 0;
|
|
||||||
UINT nMinor = 0;
|
|
||||||
if (!GetLastMajorMinorTag(lpRepoPath, strBranch, strLastTag, nMajor, nMinor))
|
if (!GetLastMajorMinorTag(lpRepoPath, strBranch, strLastTag, nMajor, nMinor))
|
||||||
{
|
{
|
||||||
nMajor = nDefaultMajor;
|
nMajor = nDefaultMajor;
|
||||||
@@ -299,18 +650,23 @@ int BuildVersionsFromRepo(
|
|||||||
_tprintf(_T("最近标签: %s (major=%u minor=%u)\n"), strLastTag.GetString(), nMajor, nMinor);
|
_tprintf(_T("最近标签: %s (major=%u minor=%u)\n"), strLastTag.GetString(), nMajor, nMinor);
|
||||||
}
|
}
|
||||||
|
|
||||||
UINT nYY = 0;
|
if (!ResolveFileVersionDateAndCommitCount(lpRepoPath, strBranch, nYY, nMMDD, nId, pOutFileVersionDate))
|
||||||
UINT nMMDD = 0;
|
|
||||||
UINT nId = 0;
|
|
||||||
if (!ResolveFileVersionDateAndCommitCount(lpRepoPath, strBranch, nYY, nMMDD, nId))
|
|
||||||
{
|
{
|
||||||
_tprintf(_T("错误: 获取分支提交信息失败\n"));
|
_tprintf(_T("错误: 获取分支提交信息失败\n"));
|
||||||
return errorCodes.nCommitErrorCode;
|
return errorCodes.nCommitErrorCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pOutBranch != NULL)
|
||||||
|
{
|
||||||
|
*pOutBranch = strBranch;
|
||||||
|
}
|
||||||
|
|
||||||
_tprintf(_T("分支提交信息: yy=%u mmdd=%u id=%u\n"), nYY, nMMDD, nId);
|
_tprintf(_T("分支提交信息: yy=%u mmdd=%u id=%u\n"), nYY, nMMDD, nId);
|
||||||
|
}
|
||||||
|
|
||||||
strProductVersion.Format(_T("%u.%u.%u.%u"), nPid, nBid, nMajor, nMinor);
|
strProductVersion.Format(_T("%u.%u.%u.%u"), nPid, nBid, nMajor, nMinor);
|
||||||
strFileVersion.Format(_T("%u.%u.%u.%u"), nPid, nYY, nMMDD, nId);
|
strFileVersion.Format(_T("%u.%u.%u.%u"), nPid, nYY, nMMDD, nId);
|
||||||
_tprintf(_T("文件版本信息: ProductVersion=%s FileVersion=%s\n"), strProductVersion.GetString(), strFileVersion.GetString());
|
_tprintf(_T("文件版本信息: ProductVersion=%s FileVersion=%s\n"), strProductVersion.GetString(), strFileVersion.GetString());
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -1,16 +1,86 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// BuildVersionsFromRepo 各步骤失败时使用的退出码映射。
|
||||||
|
/// </summary>
|
||||||
struct VersionBuildErrorCodes
|
struct VersionBuildErrorCodes
|
||||||
{
|
{
|
||||||
int nBidErrorCode;
|
int nBidErrorCode;
|
||||||
int nTagErrorCode;
|
int nTagErrorCode;
|
||||||
int nCommitErrorCode;
|
int nCommitErrorCode;
|
||||||
|
int nStatusErrorCode;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 检测 Git 仓库是否处于正常分支状态(非 HEAD 游离)。
|
||||||
|
/// </summary>
|
||||||
|
BOOL TryGetGitStatus(LPCTSTR lpRepoPath, CString& strStatus);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取当前检出的本地分支名称。
|
||||||
|
/// </summary>
|
||||||
BOOL TryGetCurrentBranch(LPCTSTR lpRepoPath, CString& strBranch);
|
BOOL TryGetCurrentBranch(LPCTSTR lpRepoPath, CString& strBranch);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 在 HEAD 游离时推断当前所属远程分支名。
|
||||||
|
/// </summary>
|
||||||
|
BOOL TryGetDetectedBranch(LPCTSTR lpRepoPath, CString& strBranch);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取指向当前 HEAD 的 Tag 名称。
|
||||||
|
/// </summary>
|
||||||
|
BOOL TryGetDetectedTag(LPCTSTR lpRepoPath, CString& strTag);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从分支名解析 bid(分支 ID)。
|
||||||
|
/// </summary>
|
||||||
BOOL TryGetBidFromBranch(const CString& strBranch, UINT& nBid);
|
BOOL TryGetBidFromBranch(const CString& strBranch, UINT& nBid);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 解析符合「分支名.major.minor」格式的 Tag。
|
||||||
|
/// </summary>
|
||||||
BOOL TryParseTagMajorMinor(const CString& strBranch, const CString& strTag, UINT& nMajor, UINT& nMinor);
|
BOOL TryParseTagMajorMinor(const CString& strBranch, const CString& strTag, UINT& nMajor, UINT& nMinor);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取当前分支下版本号最大的 major.minor Tag。
|
||||||
|
/// </summary>
|
||||||
BOOL GetLastMajorMinorTag(LPCTSTR lpRepoPath, const CString& strBranch, CString& strTag, UINT& nMajor, UINT& nMinor);
|
BOOL GetLastMajorMinorTag(LPCTSTR lpRepoPath, const CString& strBranch, CString& strTag, UINT& nMajor, UINT& nMinor);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 基于 Tag 提交日期解析 FileVersion 的 yy、mmdd 与 id。
|
||||||
|
/// </summary>
|
||||||
|
BOOL ResolveTagFileVersionDateAndCommitCount(
|
||||||
|
LPCTSTR lpRepoPath,
|
||||||
|
const CString& strBranch,
|
||||||
|
const CString& strTag,
|
||||||
|
UINT& nYY,
|
||||||
|
UINT& nMMDD,
|
||||||
|
UINT& nId,
|
||||||
|
SYSTEMTIME* pOutDate = NULL);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 基于当前或最近提交日解析 FileVersion 的 yy、mmdd 与 id。
|
||||||
|
/// </summary>
|
||||||
|
BOOL ResolveFileVersionDateAndCommitCount(
|
||||||
|
LPCTSTR lpRepoPath,
|
||||||
|
const CString& strBranch,
|
||||||
|
UINT& nYY,
|
||||||
|
UINT& nMMDD,
|
||||||
|
UINT& nId,
|
||||||
|
SYSTEMTIME* pOutDate = NULL);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取指定日期内 origin/分支 的提交摘要列表。
|
||||||
|
/// </summary>
|
||||||
|
BOOL TryGetGitLogByBranchDate(
|
||||||
|
LPCTSTR lpRepoPath,
|
||||||
|
const CString& strBranch,
|
||||||
|
const SYSTEMTIME& stDate,
|
||||||
|
CString& strLog);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从 Git 仓库信息构建 ProductVersion 与 FileVersion 字符串。
|
||||||
|
/// </summary>
|
||||||
int BuildVersionsFromRepo(
|
int BuildVersionsFromRepo(
|
||||||
LPCTSTR lpRepoPath,
|
LPCTSTR lpRepoPath,
|
||||||
UINT nPid,
|
UINT nPid,
|
||||||
@@ -18,4 +88,6 @@ int BuildVersionsFromRepo(
|
|||||||
CString& strProductVersion,
|
CString& strProductVersion,
|
||||||
CString& strFileVersion,
|
CString& strFileVersion,
|
||||||
UINT nDefaultMajor,
|
UINT nDefaultMajor,
|
||||||
UINT nDefaultMinor);
|
UINT nDefaultMinor,
|
||||||
|
CString* pOutBranch = NULL,
|
||||||
|
SYSTEMTIME* pOutFileVersionDate = NULL);
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "targetver.h"
|
#include "targetver.h"
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <tchar.h>
|
#include <tchar.h>
|
||||||
#define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS // 部分 CString 构造函数将是显式的
|
#define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS // 部分 CString 构造函数将是显式的
|
||||||
#define _AFX_NO_MFC_CONTROLS_IN_DIALOGS // 移除对话框中的 MFC 控件支持
|
#define _AFX_NO_MFC_CONTROLS_IN_DIALOGS // 移除对话框中的 MFC 控件支持
|
||||||
|
|
||||||
#ifndef VC_EXTRALEAN
|
#ifndef VC_EXTRALEAN
|
||||||
#define VC_EXTRALEAN // 从 Windows 头文件中排除极少使用的内容
|
#define VC_EXTRALEAN // 从 Windows 头文件中排除极少使用的内容
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <afx.h>
|
#include <afx.h>
|
||||||
#include <afxwin.h> // MFC 核心组件和标准组件
|
#include <afxwin.h> // MFC 核心组件和标准组件
|
||||||
#include <afxext.h> // MFC 扩展
|
#include <afxext.h> // MFC 扩展
|
||||||
#ifndef _AFX_NO_OLE_SUPPORT
|
#ifndef _AFX_NO_OLE_SUPPORT
|
||||||
#include <afxdtctl.h> // MFC 对 Internet Explorer 4 公共控件的支持
|
#include <afxdtctl.h> // MFC 对 Internet Explorer 4 公共控件的支持
|
||||||
#endif
|
#endif
|
||||||
#ifndef _AFX_NO_AFXCMN_SUPPORT
|
#ifndef _AFX_NO_AFXCMN_SUPPORT
|
||||||
#include <afxcmn.h> // MFC 对 Windows 公共控件的支持
|
#include <afxcmn.h> // MFC 对 Windows 公共控件的支持
|
||||||
#endif // _AFX_NO_AFXCMN_SUPPORT
|
#endif // _AFX_NO_AFXCMN_SUPPORT
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|||||||
20834
GitVer/httplib.h
Normal file
20834
GitVer/httplib.h
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
|||||||
// pch.cpp: 与预编译标头对应的源文件
|
// pch.cpp: 与预编译标头对应的源文件
|
||||||
|
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
|
|
||||||
// 当使用预编译的头时,需要使用此源文件,编译才能成功。
|
// 当使用预编译的头时,需要使用此源文件,编译才能成功。
|
||||||
12
GitVer/pch.h
12
GitVer/pch.h
@@ -1,13 +1,13 @@
|
|||||||
// pch.h: 这是预编译标头文件。
|
// pch.h: 这是预编译标头文件。
|
||||||
// 下方列出的文件仅编译一次,提高了将来生成的生成性能。
|
// 下方列出的文件仅编译一次,提高了将来生成的生成性能。
|
||||||
// 这还将影响 IntelliSense 性能,包括代码完成和许多代码浏览功能。
|
// 这还将影响 IntelliSense 性能,包括代码完成和许多代码浏览功能。
|
||||||
// 但是,如果此处列出的文件中的任何一个在生成之间有更新,它们全部都将被重新编译。
|
// 但是,如果此处列出的文件中的任何一个在生成之间有更新,它们全部都将被重新编译。
|
||||||
// 请勿在此处添加要频繁更新的文件,这将使得性能优势无效。
|
// 请勿在此处添加要频繁更新的文件,这将使得性能优势无效。
|
||||||
|
|
||||||
#ifndef PCH_H
|
#ifndef PCH_H
|
||||||
#define PCH_H
|
#define PCH_H
|
||||||
|
|
||||||
// 添加要在此处预编译的标头
|
// 添加要在此处预编译的标头
|
||||||
#include "framework.h"
|
#include "framework.h"
|
||||||
|
|
||||||
#endif //PCH_H
|
#endif //PCH_H
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
//{{NO_DEPENDENCIES}}
|
//{{NO_DEPENDENCIES}}
|
||||||
// Microsoft Visual C++ 生成的包含文件。
|
// Microsoft Visual C++ 生成的包含文件。
|
||||||
// 供 GitVer.rc 使用
|
// 供 GitVer.rc 使用
|
||||||
//
|
//
|
||||||
#define IDI_ICON_APP 100
|
#define IDI_ICON_APP 100
|
||||||
#define IDI_ICON1 101
|
#define IDI_ICON1 101
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
// // 包含 SDKDDKVer.h 可定义可用的最高版本的 Windows 平台。
|
// // 包含 SDKDDKVer.h 可定义可用的最高版本的 Windows 平台。
|
||||||
// 如果希望为之前的 Windows 平台构建应用程序,在包含 SDKDDKVer.h 之前请先包含 WinSDKVer.h 并
|
// 如果希望为之前的 Windows 平台构建应用程序,在包含 SDKDDKVer.h 之前请先包含 WinSDKVer.h 并
|
||||||
// 将 _WIN32_WINNT 宏设置为想要支持的平台。
|
// 将 _WIN32_WINNT 宏设置为想要支持的平台。
|
||||||
#include <SDKDDKVer.h>
|
#include <SDKDDKVer.h>
|
||||||
|
|||||||
197
README.md
197
README.md
@@ -1,6 +1,7 @@
|
|||||||
# GitVer
|
# GitVer
|
||||||
|
|
||||||
命令行版本管理工具,从 Git 仓库读取 tag 自动生成并回写版本号。
|
命令行版本管理工具,从 Git 仓库读取 tag 自动生成并回写版本号。
|
||||||
|
必须安装Git for Windows,且安装目录默认在C盘:C:\Program Files\Git
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -13,7 +14,8 @@ gitver setver=<pid> [repodir=<path>] [-test] [-setup=0|1]
|
|||||||
gitver nuitkabuild=<pid> [repodir=<path>] [-test] [params="<mainPy> <nuitka参数>"]
|
gitver nuitkabuild=<pid> [repodir=<path>] [-test] [params="<mainPy> <nuitka参数>"]
|
||||||
gitver nuitkapydbuild=<pid> [repodir=<path>] [-test] [params="<modulePy> <nuitka参数>"]
|
gitver nuitkapydbuild=<pid> [repodir=<path>] [-test] [params="<modulePy> <nuitka参数>"]
|
||||||
gitver pyinstaller=<pid> [repodir=<path>] [-test] [params="<mainPy|specFile> <pyinstaller参数>"]
|
gitver pyinstaller=<pid> [repodir=<path>] [-test] [params="<mainPy|specFile> <pyinstaller参数>"]
|
||||||
gitver -setup=0|1 <pid> [repodir=<path>] [-test]
|
gitver setup=n [pid=m] [repodir=<path>] [-test]
|
||||||
|
gitver upload username=<用户名> [file=<上传文件>] [info=<配置.txt>] [url=<PMC基址>]
|
||||||
```
|
```
|
||||||
|
|
||||||
**通用参数说明:**
|
**通用参数说明:**
|
||||||
@@ -94,7 +96,7 @@ gitver rewrite -f
|
|||||||
从 Git 仓库读取 tag,生成产品版本号与文件版本号,并按源码类型自动回写。
|
从 Git 仓库读取 tag,生成产品版本号与文件版本号,并按源码类型自动回写。
|
||||||
|
|
||||||
```
|
```
|
||||||
gitver setver=<pid> [repodir=<path>] [-test] [-setup=0|1]
|
gitver setver=<pid> [repodir=<path>] [-test]
|
||||||
```
|
```
|
||||||
|
|
||||||
**版本号生成规则:**
|
**版本号生成规则:**
|
||||||
@@ -102,26 +104,12 @@ gitver setver=<pid> [repodir=<path>] [-test] [-setup=0|1]
|
|||||||
- `ProductVersion = pid.bid.major.minor`(取当前分支前缀的最新 tag)
|
- `ProductVersion = pid.bid.major.minor`(取当前分支前缀的最新 tag)
|
||||||
- `FileVersion = pid.yy.mmdd.id`(id = 当天当前分支提交次数)
|
- `FileVersion = pid.yy.mmdd.id`(id = 当天当前分支提交次数)
|
||||||
|
|
||||||
**`-setup=N` 标志(打包模式):**
|
|
||||||
|
|
||||||
存在 `-setup=N` 时,**不回写**版本信息到项目文件,改为:
|
|
||||||
1. 在 exe 目录或上级目录查找安装脚本
|
|
||||||
2. 修改脚本中的版本号字段
|
|
||||||
3. 调用对应编译器打包
|
|
||||||
|
|
||||||
| 值 | 脚本文件 | 编译器 |
|
|
||||||
|---|---|---|
|
|
||||||
| `0` | `setup.iss` | Inno Setup(`ISCC.exe`) |
|
|
||||||
| `1` | `setup.nsi` | NSIS(`makensis.exe`) |
|
|
||||||
|
|
||||||
**示例:**
|
**示例:**
|
||||||
|
|
||||||
```
|
```
|
||||||
gitver setver=5
|
gitver setver=5
|
||||||
gitver setver=5 repodir=E:\Code\OTH\gitver
|
gitver setver=5 repodir=E:\Code\OTH\gitver
|
||||||
gitver setver=5 -test
|
gitver setver=5 -test
|
||||||
gitver setver=5 -setup=0
|
|
||||||
gitver setver=5 repodir=E:\Code\MyProj -setup=1
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**输出示例:**
|
**输出示例:**
|
||||||
@@ -129,8 +117,21 @@ gitver setver=5 repodir=E:\Code\MyProj -setup=1
|
|||||||
```
|
```
|
||||||
ProductVersion=5.0.1920.11
|
ProductVersion=5.0.1920.11
|
||||||
FileVersion=5.26.0519.3
|
FileVersion=5.26.0519.3
|
||||||
|
已更新 upload_info.txt: E:\Code\MyProj\upload_info.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**upload_info.txt 自动更新:**
|
||||||
|
|
||||||
|
`setver` 成功生成版本号后,会在仓库目录(`repodir`,缺省为当前目录)写入或更新 `upload_info.txt`:
|
||||||
|
|
||||||
|
| 字段 | 来源 |
|
||||||
|
|---|---|
|
||||||
|
| `product_version` | 本次生成的 ProductVersion |
|
||||||
|
| `file_version` | 本次生成的 FileVersion |
|
||||||
|
| `file_change_log` | 文件版本对应日期的 git 提交说明(`origin/<分支>` 当天 log,多条以换行分隔) |
|
||||||
|
|
||||||
|
`setup` 成功编译安装包后,还会写入 `file`(安装包路径)。文件中已有的其他字段(如 `package_name`、`package_type_id`、`url` 等)会保留不变。
|
||||||
|
|
||||||
**回写说明:**
|
**回写说明:**
|
||||||
|
|
||||||
- **C++**:回写 `.rc` 文件中的 FILEVERSION、PRODUCTVERSION 及对应字符串字段
|
- **C++**:回写 `.rc` 文件中的 FILEVERSION、PRODUCTVERSION 及对应字符串字段
|
||||||
@@ -215,14 +216,23 @@ gitver pyinstaller=<pid> [repodir=<path>] [-test] [params="<mainPy|specFile> <py
|
|||||||
python -m PyInstaller <params>
|
python -m PyInstaller <params>
|
||||||
```
|
```
|
||||||
|
|
||||||
版本号照常从 Git tag 计算并输出,但 PyInstaller 本身不接受 `--windows-product-version` 等命令行参数。如需将版本信息嵌入 EXE,建议在 `.spec` 文件中通过 `version=` 字段或外部 version resource 文件实现。
|
版本号照常从 Git tag 计算并输出。在 **spec 模式**下,gitver 会在调用 PyInstaller 前自动将版本号写入版本信息,具体行为取决于 spec 文件中 `version=` 字段的值:
|
||||||
|
|
||||||
|
| spec 中 `version=` 的值 | 行为 |
|
||||||
|
|---|---|
|
||||||
|
| `'version_info.txt'`(引号路径,文件存在) | 就地更新外部版本信息文件 |
|
||||||
|
| `'1.2.3.4'`(引号字符串,非有效文件路径) | 替换 spec 内该引号字符串为新版本号 |
|
||||||
|
| `version_info`(无引号标识符,spec 内定义的 VSVersionInfo 对象) | 就地更新 spec 文件内的版本字段 |
|
||||||
|
| 无 `version=` / `version=None` | 在 spec 目录生成 `version_info.txt` 并自动注入 `version='version_info.txt'` |
|
||||||
|
|
||||||
|
在 **脚本模式**(`.py` 入口)下,PyInstaller 不接受命令行版本参数,版本号仅输出到控制台,不嵌入 EXE。
|
||||||
|
|
||||||
**两种打包模式:**
|
**两种打包模式:**
|
||||||
|
|
||||||
| 模式 | 参数示例 | 说明 |
|
| 模式 | 参数示例 | 说明 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| 脚本模式 | `params="main.py"` | 由 PyInstaller 自动生成 spec 并打包 |
|
| 脚本模式 | `params="main.py"` | 由 PyInstaller 自动生成 spec 并打包 |
|
||||||
| spec 模式 | `params="myapp.spec"` | 使用已有 spec 文件,打包配置完全由 spec 控制,`params=` 仅适用于路径类运行时选项(`--distpath` 等) |
|
| spec 模式 | `params="myapp.spec"` | 使用已有 spec 文件,打包配置完全由 spec 控制;gitver 在调用 PyInstaller 前**自动将版本号写入版本信息**(见下表) |
|
||||||
|
|
||||||
**示例:**
|
**示例:**
|
||||||
|
|
||||||
@@ -247,19 +257,104 @@ FileVersion=5.26.0519.3
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### `gitver -setup=0|1`(独立打包命令)
|
### `gitver setup=n`(独立打包命令)
|
||||||
|
|
||||||
独立调用安装脚本打包,不依赖 `setver=` 命令。
|
独立调用安装脚本打包,不依赖其他命令。
|
||||||
|
|
||||||
```
|
```
|
||||||
gitver -setup=0|1 <pid> [repodir=<path>] [-test]
|
gitver setup=n [pid=m] [repodir=<path>] [-test]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
| 参数 | 说明 |
|
||||||
|
|---|---|
|
||||||
|
| `n` | 安装脚本类型,`0`=Inno Setup(`setup.iss`),`1`=NSIS(`setup.nsi`) |
|
||||||
|
| `pid=m` | 可选,产品 ID(整数 0-65535);不指定时自动从项目的 `.rc`、`AssemblyInfo.cs` 或 `.spec` 文件中读取版本信息 |
|
||||||
|
|
||||||
**示例:**
|
**示例:**
|
||||||
|
|
||||||
```
|
```
|
||||||
gitver -setup=0 5
|
gitver setup=0
|
||||||
gitver -setup=1 5 repodir=E:\Code\MyProj
|
gitver setup=1
|
||||||
|
gitver setup=0 pid=5
|
||||||
|
gitver setup=1 pid=5 repodir=E:\Code\MyProj
|
||||||
|
```
|
||||||
|
|
||||||
|
**upload_info.txt 自动更新:**
|
||||||
|
|
||||||
|
`setup` 编译成功后,会在仓库目录(`repodir`,缺省为当前目录)写入或更新 `upload_info.txt` 的 `file` 字段(安装包完整路径)。路径从 ISCC/NSIS 编译输出中解析;若无法解析,则尝试脚本目录下 `Output\*.exe` 或脚本目录内最新 `.exe`。
|
||||||
|
|
||||||
|
**输出示例:**
|
||||||
|
|
||||||
|
```
|
||||||
|
安装包路径: E:\Code\MyProj\Output\MyApp_Setup_5.26.0624.3.exe
|
||||||
|
已更新 upload_info.txt: E:\Code\MyProj\upload_info.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `gitver upload`(上传包到 PMC)
|
||||||
|
|
||||||
|
将构建产物上传到 PMC 包管理中心。需 PMC 服务已运行,且账号具有管理员权限。
|
||||||
|
|
||||||
|
```
|
||||||
|
gitver upload username=<用户名> [file=<上传文件>] [info=<配置.txt>] [url=<PMC基址>]
|
||||||
|
gitver upload username=<用户名> # 需在分支上且 HEAD 指向 Tag
|
||||||
|
gitver upload username=<用户名> -f # 游离 HEAD 指向 Tag 时强制上传
|
||||||
|
gitver upload -f username=<用户名> [file=<上传文件>] # -f 位置任意
|
||||||
|
```
|
||||||
|
|
||||||
|
| 参数 | 说明 |
|
||||||
|
|---|---|
|
||||||
|
| `username=` | PMC 登录账号(必填);登录密码从**同名环境变量**读取,如 `username=admin` 读取 `%admin%` |
|
||||||
|
| `file=` | 可选,要上传的包文件路径;省略时从 info 文件的 `file` 字段读取(通常由 `setup` 写入) |
|
||||||
|
| `info=` | 可选,上传配置信息文件路径,默认 `upload_info.txt`(见下方格式) |
|
||||||
|
| `url=` | 可选,PMC 服务基址,默认 `http://127.0.0.1:8000` |
|
||||||
|
|
||||||
|
**info 配置文件格式**(UTF-8 或 ANSI,每行 `key=value`,`#` 开头为注释):
|
||||||
|
|
||||||
|
```ini
|
||||||
|
url=http://127.0.0.1:8000
|
||||||
|
|
||||||
|
package_name=daily-report
|
||||||
|
package_type_id=1
|
||||||
|
file=E:\dist\MyApp_Setup_5.26.0624.3.exe
|
||||||
|
product_version=5.0.1920.11
|
||||||
|
file_version=5.26.0624.3
|
||||||
|
file_change_log=修复导出 bug
|
||||||
|
description=日报工具
|
||||||
|
wiki_url=https://example.com/wiki
|
||||||
|
product_change_log=首次发布
|
||||||
|
```
|
||||||
|
|
||||||
|
| info 字段 | 必填 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `package_name` | 是 | 包名称 |
|
||||||
|
| `package_type_id` | 是 | 包类型 ID(整数) |
|
||||||
|
| `file` | 否* | 安装包路径;`upload` 省略 `file=` 时必填,通常由 `setup` 自动写入 |
|
||||||
|
| `product_version` | 是 | 产品版本号 |
|
||||||
|
| `file_version` | 是 | 文件版本号 |
|
||||||
|
| `file_change_log` | 是 | 文件版本更新说明 |
|
||||||
|
| `description` | 否 | 包描述 |
|
||||||
|
| `wiki_url` | 否 | Wiki 链接 |
|
||||||
|
| `product_change_log` | 否 | 产品版本更新说明 |
|
||||||
|
| `url` | 否 | PMC 基址,默认 `http://127.0.0.1:8000` |
|
||||||
|
|
||||||
|
上传流程:先 `POST /auth/login` 获取 Session,再 `POST /admin/packages/upload`(multipart/form-data),最后通过 `/api/packages/latest` 验证版本是否写入。
|
||||||
|
|
||||||
|
HTTP 实现基于 [cpp-httplib](https://github.com/yhirose/cpp-httplib)([`GitVer/httplib.h`](GitVer/httplib.h)),默认支持 `http://`;若需 `https://` 须另行启用 OpenSSL 支持。
|
||||||
|
|
||||||
|
**示例:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# 先设置与 username 同名的环境变量作为登录密码
|
||||||
|
$env:admin = "your_admin_password"
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
gitver upload username=admin
|
||||||
|
gitver upload username=admin info=E:\upload_info.txt
|
||||||
|
gitver upload username=admin file=E:\dist\app.zip info=E:\upload_info.txt
|
||||||
|
gitver upload username=admin url=http://192.168.1.10:8000
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -459,7 +554,7 @@ VSVersionInfo(
|
|||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
> `version_info.txt` 中的版本号需与实际构建版本保持一致。如需自动同步,可在构建脚本中先调用 `gitver setver=<pid>` 获取版本,再用脚本替换 `version_info.txt` 中的版本字段,然后执行 `gitver pyinstaller=<pid> params="myapp.spec"`。
|
> 在 spec 模式下,gitver 会在调用 PyInstaller 前自动将计算出的版本号写入版本信息,无需手动维护 `version_info.txt`。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -491,7 +586,7 @@ VSVersionInfo(
|
|||||||
| 33 | 无参数模式下创建 tag 失败 |
|
| 33 | 无参数模式下创建 tag 失败 |
|
||||||
| 34 | 无参数模式下创建 tag 后校验失败 |
|
| 34 | 无参数模式下创建 tag 后校验失败 |
|
||||||
| 35 | 未找到可回写目标文件,或回写失败 |
|
| 35 | 未找到可回写目标文件,或回写失败 |
|
||||||
| 36 | `-setup=` 值不支持(仅支持 0 和 1) |
|
| 36 | `setup=` 值不支持(仅支持 0 和 1) |
|
||||||
| 37 | 未找到安装脚本(setup.iss / setup.nsi) |
|
| 37 | 未找到安装脚本(setup.iss / setup.nsi) |
|
||||||
| 38 | 修改安装脚本失败 |
|
| 38 | 修改安装脚本失败 |
|
||||||
| 39 | 未找到安装编译器(ISCC.exe / makensis.exe) |
|
| 39 | 未找到安装编译器(ISCC.exe / makensis.exe) |
|
||||||
@@ -500,6 +595,15 @@ VSVersionInfo(
|
|||||||
| 42 | `pyinstaller=` 无法根据分支名计算 bid |
|
| 42 | `pyinstaller=` 无法根据分支名计算 bid |
|
||||||
| 43 | `pyinstaller=` 读取 tag 失败 |
|
| 43 | `pyinstaller=` 读取 tag 失败 |
|
||||||
| 44 | `pyinstaller=` 获取当天分支提交次数失败 |
|
| 44 | `pyinstaller=` 获取当天分支提交次数失败 |
|
||||||
|
| 45 | `pyinstaller=` 版本信息文件无法生成或更新 |
|
||||||
|
| 46 | `pyinstaller=` spec 文件无法注入或修改版本信息 |
|
||||||
|
| 47 | `upload` 缺少必填参数(username),或 file 未指定且 info 中无 file 字段 |
|
||||||
|
| 48 | `upload` 上传文件不存在 |
|
||||||
|
| 49 | `upload` info 文件无效、缺少必填字段,或未设置与 username 同名的环境变量 |
|
||||||
|
| 50 | `upload` PMC 登录失败 |
|
||||||
|
| 51 | `upload` HTTP 上传请求失败 |
|
||||||
|
| 52 | `upload` 上传后未找到对应版本(校验失败或版本冲突) |
|
||||||
|
| 53 | `upload` 网络/HTTP 错误 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -576,12 +680,25 @@ gitver pyinstaller=5 params="main.py --onefile --name=MyApp"
|
|||||||
# 预期: 额外参数传递给 PyInstaller
|
# 预期: 额外参数传递给 PyInstaller
|
||||||
```
|
```
|
||||||
|
|
||||||
### 验证 `-setup=`
|
### 验证 `setup=`
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 确保 exe 目录或上级目录有 setup.iss
|
# 确保 exe 目录或上级目录有 setup.iss
|
||||||
gitver setver=5 -setup=0
|
gitver setup=0
|
||||||
# 预期: 修改 setup.iss 中的版本号并调用 ISCC.exe 编译
|
# 预期: 从 .rc 或 AssemblyInfo.cs 读取版本号,修改 setup.iss 并调用 ISCC.exe 编译
|
||||||
|
|
||||||
|
gitver setup=0 pid=5
|
||||||
|
# 预期: 使用 pid=5 生成版本号,修改 setup.iss 并调用 ISCC.exe 编译
|
||||||
|
```
|
||||||
|
|
||||||
|
### 验证 upload
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# 前提:PMC 运行于 http://127.0.0.1:8000,且已有 admin 账号
|
||||||
|
$env:admin = "your_admin_password"
|
||||||
|
# 准备 upload_info.txt(含 package_name、file 等字段,不含 password)
|
||||||
|
gitver upload username=admin
|
||||||
|
# 预期: 读取当前目录 upload_info.txt,返回码 0,PMC 管理页可见新版本
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -604,8 +721,6 @@ gitver setver=5 -setup=0
|
|||||||
| `gitver setver=5` | 0 |
|
| `gitver setver=5` | 0 |
|
||||||
| `gitver setver=5 -test` | 0 |
|
| `gitver setver=5 -test` | 0 |
|
||||||
| `gitver setver=5 repodir=E:\NotExists` | 23 |
|
| `gitver setver=5 repodir=E:\NotExists` | 23 |
|
||||||
| `gitver setver=5 -setup=0` | 0 / 37 / 38 / 39 |
|
|
||||||
| `gitver setver=5 -setup=9` | 36 |
|
|
||||||
| `gitver setver=abc` | 4 |
|
| `gitver setver=abc` | 4 |
|
||||||
|
|
||||||
### nuitkabuild= 组
|
### nuitkabuild= 组
|
||||||
@@ -641,6 +756,28 @@ gitver setver=5 -setup=0
|
|||||||
| `gitver pyinstaller=5` | 0 |
|
| `gitver pyinstaller=5` | 0 |
|
||||||
| `gitver pyinstaller=abc params="main.py"` | 41 |
|
| `gitver pyinstaller=abc params="main.py"` | 41 |
|
||||||
|
|
||||||
|
### setup= 组
|
||||||
|
|
||||||
|
| 命令 | 预期返回码 |
|
||||||
|
|---|---|
|
||||||
|
| `gitver setup=0` | 0 / 37 / 38 / 39 |
|
||||||
|
| `gitver setup=1` | 0 / 37 / 38 / 39 |
|
||||||
|
| `gitver setup=0 pid=5` | 0 / 37 / 38 / 39 |
|
||||||
|
| `gitver setup=0 pid=5 repodir=E:\NotExists` | 23 |
|
||||||
|
| `gitver setup=9` | 36 |
|
||||||
|
| `gitver setup=0 pid=abc` | 4 |
|
||||||
|
|
||||||
|
### upload 组
|
||||||
|
|
||||||
|
| 命令 | 预期返回码 |
|
||||||
|
|---|---|
|
||||||
|
| `gitver upload username=admin` | 0 / 47 / 48 / 49 / 50 / 52 / 53 |
|
||||||
|
| `gitver upload username=admin info=E:\upload_info.txt` | 0 / 50 / 52 / 53 |
|
||||||
|
| `gitver upload username=admin file=E:\dist\app.zip info=E:\upload_info.txt` | 0 / 50 / 52 / 53 |
|
||||||
|
| `gitver upload username=admin file=E:\NotExists info=E:\upload_info.txt` | 48 |
|
||||||
|
| `gitver upload username=admin info=E:\NotExists` | 49 |
|
||||||
|
| `gitver upload` | 47 |
|
||||||
|
|
||||||
> **注意**:如果打包时提示“未找到安装编译器(ISCC.exe / makensis.exe)”,请手动下载安装对应工具,并将其可执行文件路径(如 `ISCC.exe` 或 `makensis.exe` 所在目录)添加到系统环境变量 `PATH`。常见下载地址:
|
> **注意**:如果打包时提示“未找到安装编译器(ISCC.exe / makensis.exe)”,请手动下载安装对应工具,并将其可执行文件路径(如 `ISCC.exe` 或 `makensis.exe` 所在目录)添加到系统环境变量 `PATH`。常见下载地址:
|
||||||
>
|
>
|
||||||
> - Inno Setup: https://jrsoftware.org/isinfo.php
|
> - Inno Setup: https://jrsoftware.org/isinfo.php
|
||||||
|
|||||||
Reference in New Issue
Block a user