1、setup=n为独立命令行,不再使用-setup=n方式。

2、更新md说明
This commit is contained in:
2026-05-21 17:30:46 +08:00
parent 84d67d9709
commit 4a4c51d124
5 changed files with 240 additions and 121 deletions

View File

@@ -354,9 +354,10 @@ void PrintFullUsageExamples()
_T(" gitver nuitkabuild=<pid> [repodir=<path>可选] [-test可选] [params=\"<mainPy> <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 -setup=0|1 [pid] [repodir=<path>可选] [-test可选]\n")
_T(" -setup=0: 使用 Inno Setup 脚本 (setup.iss)\n")
_T(" -setup=1: 使用 NSIS 脚本 (setup.nsh)\n")
_T(" gitver setup=n [pid=m可选] [repodir=<path>可选] [-test可选]\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(" gitver\n")
_T(" gitver rewrite\n")
@@ -377,8 +378,9 @@ void PrintFullUsageExamples()
_T(" gitver pyinstaller=5 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 -setup=0 5\n")
_T(" gitver -setup=1 5 repodir=E:\\Code\\MyProj\n")
_T(" gitver setup=0\n")
_T(" gitver setup=0 pid=5\n")
_T(" gitver setup=1 pid=5 repodir=E:\\Code\\MyProj\n")
_T("params: 传递给 Nuitka/PyInstaller 的参数(含入口文件),用双引号括起来,如 params=\"main.py --standalone --output-dir=dist\"\n")
_T("-test: 将产品版本号的 major 和 minor 都置为 0用于测试版本构建\n")
_T("无参数时会读取当前分支最近三次 tag并提示选择 major 加 1 或 minor 加 1然后创建新 tag。\n")
@@ -484,7 +486,7 @@ LPCTSTR GetExitCodeHint(int nRetCode)
case 35:
return _T("rewrite version file failed");
case 36:
return _T("setup: unsupported -setup value");
return _T("setup: unsupported setup value");
case 37:
return _T("setup: setup script not found");
case 38:
@@ -631,35 +633,6 @@ int HandleSetVerCommand(int argc, TCHAR* argv[])
_tprintf(_T("ProductVersion=%s\n"), strProductVersion.GetString());
_tprintf(_T("FileVersion=%s\n"), strFileVersion.GetString());
// 检查是否附带了 -setup=N 标志
int nSetupType = -1;
for (int i = 2; i < argc; ++i)
{
CString strArg = argv[i];
if (strArg.GetLength() > 7 && strArg.Left(7).CompareNoCase(_T("-setup=")) == 0)
{
UINT nVal = 0;
if (!TryParseUInt16(strArg.Mid(7), nVal) || (nVal != 0 && nVal != 1))
{
_tprintf(_T("错误: -setup 参数值 \"%s\" 不支持,仅支持 0Inno Setup或 1NSIS\n"),
strArg.Mid(7).GetString());
return 36;
}
nSetupType = (int)nVal;
break;
}
}
if (nSetupType >= 0)
{
// -setup=N 存在:不回写版本信息到项目文件,只修改安装脚本并编译
if (!ExecuteSetupBuild(nSetupType, strProductVersion, strFileVersion))
{
return 38;
}
return 0;
}
return RewriteSetVerByProjectType(lpRepoPath, strProductVersion, strFileVersion);
}
@@ -923,12 +896,12 @@ int main(int argc, TCHAR* argv[], TCHAR* envp[])
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);
LogCommandEnd(_T("-setup"), dwStartTick, nCmdRet);
PrintCommandFailedWithCode(_T("-setup"), nCmdRet);
LogCommandEnd(_T("setup"), dwStartTick, nCmdRet);
PrintCommandFailedWithCode(_T("setup"), nCmdRet);
return nCmdRet;
}

View File

@@ -24,12 +24,6 @@ int ParseSetVerOptions(int argc, TCHAR* argv[], int nStartIndex, LPCTSTR& lpRepo
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 (lpRepoPath != NULL)

View File

@@ -15,6 +15,9 @@ BOOL WriteTextFileAsAnsi(LPCTSTR lpFile, const std::string& strContent, const st
void PrintInvalidRepoPathError(LPCTSTR lpRepoPath);
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_MINOR_WHEN_NO_TAG_FOR_SETUP = 0;
@@ -246,7 +249,105 @@ 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;
}
// ─────────────────────────────────────────────
// 公共执行函数
// 不回写版本信息到项目文件,只修改安装脚本并调用编译器打包。
// ─────────────────────────────────────────────
@@ -318,7 +419,7 @@ BOOL ExecuteSetupBuild(int nSetupType, const CString& strProductVersion, const C
}
// ─────────────────────────────────────────────
// 独立命令入口argv[1] 形如 "-setup=0"
// 独立命令入口argv[1] 形如 "setup=0"
// ─────────────────────────────────────────────
int HandleSetupCommand(int argc, TCHAR* argv[])
@@ -326,73 +427,119 @@ int HandleSetupCommand(int argc, TCHAR* argv[])
#ifdef _DEBUG
Sleep(15000);
#endif
// 解析 -setup=n 中的 n
// 解析 setup=n 中的 n
CString strSetupArg = argv[1];
CString strN = strSetupArg.Mid(7); // 跳过 "-setup="
CString strN = strSetupArg.Mid(6); // 跳过 "setup="
UINT nSetupType = UINT(-1);
if (!TryParseUInt16(strN, nSetupType) || (nSetupType != 0 && nSetupType != 1))
{
_tprintf(_T("错误: -setup 参数值 \"%s\" 不支持,仅支持 0Inno Setup或 1NSIS\n"),
_tprintf(_T("错误: setup 参数值 \"%s\" 不支持,仅支持 0Inno Setup或 1NSIS\n"),
strN.GetString());
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"));
_tprintf(_T("用法gitver -setup=0|1 [pid] [repodir=<path>可选]\n"));
_tprintf(_T("示例gitver -setup=0 5\n"));
_tprintf(_T("示例gitver -setup=1 5 repodir=E:\\Code\\MyProj\n"));
CString strArg = argv[i];
if (strArg.CompareNoCase(_T("-test")) == 0)
{
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;
}
UINT nPid = 0;
int nArgRet = ParseUInt16ArgOrError(argv, 2, _T("pid"), nPid, 4);
if (nArgRet != 0)
if (lpRepoPath == NULL)
{
return nArgRet;
}
LPCTSTR lpRepoPath = NULL;
BOOL bTestMode = FALSE;
int nRepoArgRet = ParseSetVerOptions(argc, argv, 3, lpRepoPath, bTestMode);
if (nRepoArgRet != 0)
{
return nRepoArgRet;
lpRepoPath = g_szCurModuleDir;
}
CString strFileVersion;
CString strProductVersion;
VersionBuildErrorCodes errorCodes = { 5, 6, 9 };
int nVersionRet = BuildVersionsFromRepo(
lpRepoPath,
nPid,
errorCodes,
strProductVersion,
strFileVersion,
DEFAULT_MAJOR_WHEN_NO_TAG_FOR_SETUP,
DEFAULT_MINOR_WHEN_NO_TAG_FOR_SETUP);
CString strProductVersion;
if (nVersionRet != 0)
if (bHasPid)
{
return nVersionRet;
}
if (bTestMode)
{
int nDot1 = strProductVersion.Find(_T('.'));
int nDot2 = (nDot1 >= 0) ? strProductVersion.Find(_T('.'), nDot1 + 1) : -1;
if (nDot2 > nDot1)
// 从 git 仓库生成版本号
VersionBuildErrorCodes errorCodes = { 5, 6, 9 };
int nVersionRet = BuildVersionsFromRepo(
lpRepoPath,
nPid,
errorCodes,
strProductVersion,
strFileVersion,
DEFAULT_MAJOR_WHEN_NO_TAG_FOR_SETUP,
DEFAULT_MINOR_WHEN_NO_TAG_FOR_SETUP);
if (nVersionRet != 0)
{
strProductVersion = strProductVersion.Left(nDot2 + 1) + _T("0.0");
return nVersionRet;
}
_tprintf(_T("[测试版本] 已将 major/minor 置零: ProductVersion=%s\n"), strProductVersion.GetString());
if (bTestMode)
{
int nDot1 = strProductVersion.Find(_T('.'));
int nDot2 = (nDot1 >= 0) ? strProductVersion.Find(_T('.'), nDot1 + 1) : -1;
if (nDot2 > nDot1)
{
strProductVersion = strProductVersion.Left(nDot2 + 1) + _T("0.0");
}
_tprintf(_T("[测试版本] 已将 major/minor 置零: ProductVersion=%s\n"), strProductVersion.GetString());
}
_tprintf(_T("FileVersion=%s\n"), strFileVersion.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;
}
_tprintf(_T("FileVersion=%s\n"), strFileVersion.GetString());
_tprintf(_T("ProductVersion=%s\n"), strProductVersion.GetString());
// 不回写版本信息到项目文件,直接修改安装脚本并编译打包
// 修改安装脚本并编译打包
if (!ExecuteSetupBuild((int)nSetupType, strProductVersion, strFileVersion))
{
return 38;

View File

@@ -9,8 +9,8 @@
BOOL ExecuteSetupBuild(int nSetupType, const CString& strProductVersion, const CString& strFileVersion);
/// <summary>
/// 处理独立的 -setup=n 命令行入口。
/// 退出码0 成功36 -setup 值不支持37 未找到脚本,
/// 处理独立的 setup=n 命令行入口。
/// 退出码0 成功36 setup 值不支持37 未找到脚本,
/// 38 修改脚本失败39 未找到编译器,其他同通用错误码。
/// </summary>
int HandleSetupCommand(int argc, TCHAR* argv[]);

View File

@@ -13,7 +13,7 @@ gitver setver=<pid> [repodir=<path>] [-test] [-setup=0|1]
gitver nuitkabuild=<pid> [repodir=<path>] [-test] [params="<mainPy> <nuitka参数>"]
gitver nuitkapydbuild=<pid> [repodir=<path>] [-test] [params="<modulePy> <nuitka参数>"]
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]
```
**通用参数说明:**
@@ -94,7 +94,7 @@ gitver rewrite -f
从 Git 仓库读取 tag生成产品版本号与文件版本号并按源码类型自动回写。
```
gitver setver=<pid> [repodir=<path>] [-test] [-setup=0|1]
gitver setver=<pid> [repodir=<path>] [-test]
```
**版本号生成规则:**
@@ -102,26 +102,12 @@ gitver setver=<pid> [repodir=<path>] [-test] [-setup=0|1]
- `ProductVersion = pid.bid.major.minor`(取当前分支前缀的最新 tag
- `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 repodir=E:\Code\OTH\gitver
gitver setver=5 -test
gitver setver=5 -setup=0
gitver setver=5 repodir=E:\Code\MyProj -setup=1
```
**输出示例:**
@@ -247,19 +233,26 @@ 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=1 5 repodir=E:\Code\MyProj
gitver setup=0
gitver setup=1
gitver setup=0 pid=5
gitver setup=1 pid=5 repodir=E:\Code\MyProj
```
---
@@ -491,7 +484,7 @@ VSVersionInfo(
| 33 | 无参数模式下创建 tag 失败 |
| 34 | 无参数模式下创建 tag 后校验失败 |
| 35 | 未找到可回写目标文件,或回写失败 |
| 36 | `-setup=` 值不支持(仅支持 0 和 1 |
| 36 | `setup=` 值不支持(仅支持 0 和 1 |
| 37 | 未找到安装脚本setup.iss / setup.nsi |
| 38 | 修改安装脚本失败 |
| 39 | 未找到安装编译器ISCC.exe / makensis.exe |
@@ -576,12 +569,15 @@ gitver pyinstaller=5 params="main.py --onefile --name=MyApp"
# 预期: 额外参数传递给 PyInstaller
```
### 验证 `-setup=`
### 验证 `setup=`
```bash
# 确保 exe 目录或上级目录有 setup.iss
gitver setver=5 -setup=0
# 预期: 修改 setup.iss 中的版本号并调用 ISCC.exe 编译
gitver setup=0
# 预期: 从 .rc 或 AssemblyInfo.cs 读取版本号,修改 setup.iss 并调用 ISCC.exe 编译
gitver setup=0 pid=5
# 预期: 使用 pid=5 生成版本号,修改 setup.iss 并调用 ISCC.exe 编译
```
---
@@ -604,8 +600,6 @@ gitver setver=5 -setup=0
| `gitver setver=5` | 0 |
| `gitver setver=5 -test` | 0 |
| `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 |
### nuitkabuild= 组
@@ -641,6 +635,17 @@ gitver setver=5 -setup=0
| `gitver pyinstaller=5` | 0 |
| `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 |
> **注意**如果打包时提示“未找到安装编译器ISCC.exe / makensis.exe请手动下载安装对应工具并将其可执行文件路径如 `ISCC.exe` 或 `makensis.exe` 所在目录)添加到系统环境变量 `PATH`。常见下载地址:
>
> - Inno Setup: https://jrsoftware.org/isinfo.php