1、支持pyinstaller打包方式

2、将python打包其他参数都放入params中
This commit is contained in:
2026-05-20 15:59:51 +08:00
parent 4c51a7f8d5
commit d36618d101
7 changed files with 829 additions and 59 deletions

View File

@@ -7,6 +7,7 @@
#include "gitver_common.h" #include "gitver_common.h"
#include "gitver_cli.h" #include "gitver_cli.h"
#include "gitver_nuitka.h" #include "gitver_nuitka.h"
#include "gitver_pyinstaller.h"
#include "gitver_process.h" #include "gitver_process.h"
#include "gitver_rewrite.h" #include "gitver_rewrite.h"
#include "gitver_tag.h" #include "gitver_tag.h"
@@ -350,8 +351,9 @@ void PrintFullUsageExamples()
_T(" gitver (显示帮助后进入当前分支创建 tag 流程)\n") _T(" gitver (显示帮助后进入当前分支创建 tag 流程)\n")
_T(" gitver rewrite [PEType可选] [-f可选]\n") _T(" gitver rewrite [PEType可选] [-f可选]\n")
_T(" gitver setver=<pid> [repodir=<path>可选] [-test可选]\n") _T(" gitver setver=<pid> [repodir=<path>可选] [-test可选]\n")
_T(" gitver nuitkabuild=<pid> <mainPy> [repodir=<path>可选] [-test可选] [params=\"<nuitka参数>\"可选]\n") _T(" gitver nuitkabuild=<pid> [repodir=<path>可选] [-test可选] [params=\"<mainPy> <nuitka参数>\"可选]\n")
_T(" gitver nuitkapydbuild=<pid> <modulePy> [repodir=<path>可选] [-test可选] [params=\"<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(" gitver -setup=0|1 [pid] [repodir=<path>可选] [-test可选]\n")
_T(" -setup=0: 使用 Inno Setup 脚本 (setup.iss)\n") _T(" -setup=0: 使用 Inno Setup 脚本 (setup.iss)\n")
_T(" -setup=1: 使用 NSIS 脚本 (setup.nsh)\n") _T(" -setup=1: 使用 NSIS 脚本 (setup.nsh)\n")
@@ -363,15 +365,21 @@ void PrintFullUsageExamples()
_T(" gitver setver=5 repodir=E:\\Code\\OTH\\gitver\n") _T(" gitver setver=5 repodir=E:\\Code\\OTH\\gitver\n")
_T(" gitver setver=5\n") _T(" gitver setver=5\n")
_T(" gitver setver=5 -test\n") _T(" gitver setver=5 -test\n")
_T(" gitver nuitkabuild=5 main.py\n") _T(" gitver nuitkabuild=5 params=\"main.py\"\n")
_T(" gitver nuitkabuild=5 main.py -test\n") _T(" gitver nuitkabuild=5 -test params=\"main.py\"\n")
_T(" gitver nuitkabuild=5 src\\app.py repodir=E:\\Code\\MyPyProj params=\"--standalone --output-dir=dist\"\n") _T(" gitver nuitkabuild=5 repodir=E:\\Code\\MyPyProj params=\"src\\\\app.py --standalone --output-dir=dist\"\n")
_T(" gitver nuitkapydbuild=5 module.py\n") _T(" gitver nuitkapydbuild=5 params=\"module.py\"\n")
_T(" gitver nuitkapydbuild=5 module.py -test\n") _T(" gitver nuitkapydbuild=5 -test params=\"module.py\"\n")
_T(" gitver nuitkapydbuild=5 src\\core.py repodir=E:\\Code\\MyPyProj params=\"--output-dir=dist\"\n") _T(" gitver nuitkapydbuild=5 repodir=E:\\Code\\MyPyProj params=\"src\\\\core.py --output-dir=dist\"\n")
_T(" gitver pyinstaller=5 params=\"main.py\"\n")
_T(" gitver pyinstaller=5 -test params=\"main.py\"\n")
_T(" gitver pyinstaller=5 repodir=E:\\Code\\MyPyProj params=\"src\\\\app.py --onefile --name=MyApp\"\n")
_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=0 5\n")
_T(" gitver -setup=1 5 repodir=E:\\Code\\MyProj\n") _T(" gitver -setup=1 5 repodir=E:\\Code\\MyProj\n")
_T("params: 传递给 Nuitka 的额外参数,用双引号括起来,如 params=\"--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")
_T("未找到当前分支 tag 时自动使用默认版本 1.0。\n"); _T("未找到当前分支 tag 时自动使用默认版本 1.0。\n");
@@ -386,8 +394,9 @@ void PrintShortCommandUsage()
{ {
_tprintf(_T("请使用gitver rewrite [PE类型可选]\n")); _tprintf(_T("请使用gitver rewrite [PE类型可选]\n"));
_tprintf(_T("请使用gitver setver=<pid> [repodir=<path>可选]\n")); _tprintf(_T("请使用gitver setver=<pid> [repodir=<path>可选]\n"));
_tprintf(_T("gitver nuitkabuild=<pid> <mainPy> [repodir=<path>可选] [params=\"<nuitka参数>\"可选]\n")); _tprintf(_T("gitver nuitkabuild=<pid> [repodir=<path>可选] [params=\"<mainPy> <nuitka参数>\"可选]\n"));
_tprintf(_T("gitver nuitkapydbuild=<pid> <modulePy> [repodir=<path>可选] [params=\"<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"));
} }
/// <summary> /// <summary>
@@ -482,6 +491,20 @@ LPCTSTR GetExitCodeHint(int nRetCode)
return _T("setup: modify setup script failed"); return _T("setup: modify setup script failed");
case 39: case 39:
return _T("setup: compiler not found"); return _T("setup: compiler not found");
case 40:
return _T("pyinstaller: missing args");
case 41:
return _T("pyinstaller: invalid pid");
case 42:
return _T("pyinstaller: get branch or bid failed");
case 43:
return _T("pyinstaller: get tag failed");
case 44:
return _T("pyinstaller: get today commit count failed");
case 45:
return _T("pyinstaller: write version_info.txt failed");
case 46:
return _T("pyinstaller: inject version into spec file failed");
default: default:
return _T(""); return _T("");
} }
@@ -891,6 +914,15 @@ int main(int argc, TCHAR* argv[], TCHAR* envp[])
return nCmdRet; return nCmdRet;
} }
if (argc >= 2 && _tcsnicmp(argv[1], _T("pyinstaller="), 12) == 0)
{
DWORD dwStartTick = LogCommandStart(_T("pyinstaller"));
int nCmdRet = HandlePyinstallerBuildCommand(argc, argv);
LogCommandEnd(_T("pyinstaller"), dwStartTick, nCmdRet);
PrintCommandFailedWithCode(_T("pyinstaller"), nCmdRet);
return nCmdRet;
}
if (argc >= 2 && _tcsnicmp(argv[1], _T("-setup="), 7) == 0) if (argc >= 2 && _tcsnicmp(argv[1], _T("-setup="), 7) == 0)
{ {
DWORD dwStartTick = LogCommandStart(_T("-setup")); DWORD dwStartTick = LogCommandStart(_T("-setup"));

View File

@@ -149,6 +149,7 @@
<ClInclude Include="GitVer_cli.h" /> <ClInclude Include="GitVer_cli.h" />
<ClInclude Include="GitVer_common.h" /> <ClInclude Include="GitVer_common.h" />
<ClInclude Include="GitVer_nuitka.h" /> <ClInclude Include="GitVer_nuitka.h" />
<ClInclude Include="GitVer_pyinstaller.h" />
<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" />
@@ -164,6 +165,7 @@
<ClCompile Include="GitVer_cli.cpp" /> <ClCompile Include="GitVer_cli.cpp" />
<ClCompile Include="GitVer_common.cpp" /> <ClCompile Include="GitVer_common.cpp" />
<ClCompile Include="GitVer_nuitka.cpp" /> <ClCompile Include="GitVer_nuitka.cpp" />
<ClCompile Include="GitVer_pyinstaller.cpp" />
<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" />

View File

@@ -39,6 +39,9 @@
<ClInclude Include="GitVer_nuitka.h"> <ClInclude Include="GitVer_nuitka.h">
<Filter>头文件</Filter> <Filter>头文件</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="GitVer_pyinstaller.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="GitVer_process.h"> <ClInclude Include="GitVer_process.h">
<Filter>头文件</Filter> <Filter>头文件</Filter>
</ClInclude> </ClInclude>
@@ -71,6 +74,9 @@
<ClCompile Include="GitVer_nuitka.cpp"> <ClCompile Include="GitVer_nuitka.cpp">
<Filter>源文件</Filter> <Filter>源文件</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="GitVer_pyinstaller.cpp">
<Filter>源文件</Filter>
</ClCompile>
<ClCompile Include="GitVer_process.cpp"> <ClCompile Include="GitVer_process.cpp">
<Filter>源文件</Filter> <Filter>源文件</Filter>
</ClCompile> </ClCompile>

View File

@@ -8,8 +8,6 @@
extern TCHAR g_szCurModuleDir[MAX_PATH]; extern TCHAR g_szCurModuleDir[MAX_PATH];
void PrintCommandUsageAndExamples(LPCTSTR lpCommand, LPCTSTR lpRequiredArgs, LPCTSTR lpEntryExample);
const UINT DEFAULT_MAJOR_WHEN_NO_TAG_FOR_NUITKA = 1; const UINT DEFAULT_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;
@@ -19,7 +17,7 @@ static int ParseNuitkaOptions(int argc, TCHAR* argv[], LPCTSTR& lpRepoPath, CStr
strExtraArgs.Empty(); strExtraArgs.Empty();
bTestMode = FALSE; bTestMode = FALSE;
for (int i = 3; i < argc; ++i) for (int i = 2; i < argc; ++i)
{ {
CString strArg = argv[i]; CString strArg = argv[i];
@@ -95,8 +93,6 @@ static int HandleNuitkaBuildCommandCore(
BOOL bBuildModule, BOOL bBuildModule,
LPCTSTR lpStartMessage, LPCTSTR lpStartMessage,
LPCTSTR lpUsageName, LPCTSTR lpUsageName,
LPCTSTR lpEntryArgName,
int nArgLackErrorCode,
int nPidErrorCode, int nPidErrorCode,
int nBidErrorCode, int nBidErrorCode,
int nTagErrorCode, int nTagErrorCode,
@@ -111,14 +107,6 @@ static int HandleNuitkaBuildCommandCore(
return nPidErrorCode; return nPidErrorCode;
} }
if (argc < 3)
{
_tprintf(_T("错误: 参数不足,缺少 %s。\n"), lpEntryArgName);
_tprintf(_T("用法gitver %s=<pid> <%s> [repodir=<path>可选] [-test可选] [params=\"<nuitka参数>\"\u53ef选]\n"), lpUsageName, lpEntryArgName);
return nArgLackErrorCode;
}
CString strMainOrModulePy = argv[2];
LPCTSTR lpRepoPath = NULL; LPCTSTR lpRepoPath = NULL;
CString strExtraArgs; CString strExtraArgs;
BOOL bTestMode = FALSE; BOOL bTestMode = FALSE;
@@ -159,20 +147,18 @@ static int HandleNuitkaBuildCommandCore(
if (bBuildModule) if (bBuildModule)
{ {
strNuitkaCmd.Format( strNuitkaCmd.Format(
_T("cmd /c python -m nuitka --module --windows-product-version=%s --windows-file-version=%s %s %s"), _T("cmd /c python -m nuitka --module --windows-product-version=%s --windows-file-version=%s %s"),
strProductVersion.GetString(), strProductVersion.GetString(),
strFileVersion.GetString(), strFileVersion.GetString(),
strExtraArgs.GetString(), strExtraArgs.GetString());
QuoteCmdArg(strMainOrModulePy).GetString());
} }
else else
{ {
strNuitkaCmd.Format( strNuitkaCmd.Format(
_T("cmd /c python -m nuitka --windows-product-version=%s --windows-file-version=%s %s %s"), _T("cmd /c python -m nuitka --windows-product-version=%s --windows-file-version=%s %s"),
strProductVersion.GetString(), strProductVersion.GetString(),
strFileVersion.GetString(), strFileVersion.GetString(),
strExtraArgs.GetString(), strExtraArgs.GetString());
QuoteCmdArg(strMainOrModulePy).GetString());
} }
RunNuitkaBuild(lpStartMessage, strNuitkaCmd, strProductVersion, strFileVersion, lpRepoPath); RunNuitkaBuild(lpStartMessage, strNuitkaCmd, strProductVersion, strFileVersion, lpRepoPath);
@@ -188,8 +174,6 @@ int HandleNuitkaBuildCommand(int argc, TCHAR* argv[])
FALSE, FALSE,
_T("Nuitka 打包开始.."), _T("Nuitka 打包开始.."),
_T("nuitkabuild"), _T("nuitkabuild"),
_T("mainPy"),
17,
18, 18,
19, 19,
20, 20,
@@ -205,8 +189,6 @@ int HandleNuitkaPydBuildCommand(int argc, TCHAR* argv[])
TRUE, TRUE,
_T("Nuitka pyd 打包开始.."), _T("Nuitka pyd 打包开始.."),
_T("nuitkapydbuild"), _T("nuitkapydbuild"),
_T("modulePy"),
24,
25, 25,
26, 26,
27, 27,

View File

@@ -0,0 +1,560 @@
#include "pch.h"
#include "gitver.h"
#include "gitver_cli.h"
#include "gitver_common.h"
#include "gitver_process.h"
#include "gitver_version.h"
#include "gitver_pyinstaller.h"
extern TCHAR g_szCurModuleDir[MAX_PATH];
const UINT DEFAULT_MAJOR_WHEN_NO_TAG_FOR_PYINSTALLER = 1;
const UINT DEFAULT_MINOR_WHEN_NO_TAG_FOR_PYINSTALLER = 0;
static int ParsePyinstallerOptions(int argc, TCHAR* argv[], LPCTSTR& lpRepoPath, CString& strExtraArgs, BOOL& bTestMode)
{
lpRepoPath = NULL;
strExtraArgs.Empty();
bTestMode = FALSE;
for (int i = 2; i < argc; ++i)
{
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() > 7 && strArg.Left(7).CompareNoCase(_T("params=")) == 0)
{
if (!strExtraArgs.IsEmpty())
{
_tprintf(_T("错误: params 参数重复。\n"));
return 3;
}
CString strVal = strArg.Mid(7);
// 去掉外层双引号(如 params="--onefile"
if (strVal.GetLength() >= 2 && strVal[0] == _T('"') && strVal[strVal.GetLength() - 1] == _T('"'))
strVal = strVal.Mid(1, strVal.GetLength() - 2);
strExtraArgs = strVal;
continue;
}
_tprintf(_T("错误: 未知的参数 %s\n"), argv[i]);
return 3;
}
if (lpRepoPath == NULL)
{
lpRepoPath = g_szCurModuleDir;
}
return 0;
}
// 解析版本字符串 "a.b.c.d" → 四个分量
static BOOL ParseVersionTuple(LPCTSTR lpVer, UINT& v1, UINT& v2, UINT& v3, UINT& v4)
{
CString str(lpVer);
int p1 = str.Find(_T('.'));
if (p1 < 0) return FALSE;
int p2 = str.Find(_T('.'), p1 + 1);
if (p2 < 0) return FALSE;
int p3 = str.Find(_T('.'), p2 + 1);
if (p3 < 0) return FALSE;
v1 = (UINT)_ttoi(str.Left(p1));
v2 = (UINT)_ttoi(str.Mid(p1 + 1, p2 - p1 - 1));
v3 = (UINT)_ttoi(str.Mid(p2 + 1, p3 - p2 - 1));
v4 = (UINT)_ttoi(str.Mid(p3 + 1));
return TRUE;
}
// 提取 params 中第一个空格分隔的 token支持双引号括起的路径
static CString ExtractFirstToken(const CString& strParams)
{
CString str = strParams;
str.TrimLeft();
if (str.IsEmpty()) return _T("");
if (str[0] == _T('"'))
{
int nEnd = str.Find(_T('"'), 1);
if (nEnd < 0) return str.Mid(1);
return str.Mid(1, nEnd - 1);
}
int nSpace = str.Find(_T(' '));
if (nSpace < 0) return str;
return str.Left(nSpace);
}
// 生成 version_info.txt 到 lpDir 目录PyInstaller VSVersionInfo 格式)
static BOOL WriteVersionInfoFile(LPCTSTR lpDir, LPCTSTR lpProductVersion, LPCTSTR lpFileVersion, CString& strOutPath)
{
UINT pv1 = 0, pv2 = 0, pv3 = 0, pv4 = 0;
UINT fv1 = 0, fv2 = 0, fv3 = 0, fv4 = 0;
if (!ParseVersionTuple(lpProductVersion, pv1, pv2, pv3, pv4)) return FALSE;
if (!ParseVersionTuple(lpFileVersion, fv1, fv2, fv3, fv4)) return FALSE;
CStringA strProdVerA(lpProductVersion);
CStringA strFileVerA(lpFileVersion);
const char* szProdVer = strProdVerA;
const char* szFileVer = strFileVerA;
strOutPath.Format(_T("%s\\version_info.txt"), lpDir);
FILE* fp = NULL;
_tfopen_s(&fp, strOutPath.GetString(), _T("wb"));
if (!fp) return FALSE;
fprintf(fp,
"# UTF-8\n"
"VSVersionInfo(\n"
" ffi=FixedFileInfo(\n"
" filevers=(%u, %u, %u, %u),\n"
" prodvers=(%u, %u, %u, %u),\n"
" mask=0x3f,\n"
" flags=0x0,\n"
" OS=0x40004,\n"
" fileType=0x1,\n"
" subtype=0x0,\n"
" date=(0, 0)\n"
" ),\n"
" kids=[\n"
" StringFileInfo([\n"
" StringTable(\n"
" u'040904B0',\n"
" [StringStruct(u'CompanyName', u''),\n"
" StringStruct(u'FileDescription', u''),\n"
" StringStruct(u'FileVersion', u'%s'),\n"
" StringStruct(u'InternalName', u''),\n"
" StringStruct(u'LegalCopyright', u''),\n"
" StringStruct(u'OriginalFilename', u''),\n"
" StringStruct(u'ProductName', u''),\n"
" StringStruct(u'ProductVersion', u'%s'),\n"
" StringStruct(u'Comments', u''),\n"
" StringStruct(u'LegalTrademarks', u''),\n"
" ])\n"
" ]),\n"
" VarFileInfo([VarStruct(u'Translation', [0x0409, 1200])])\n"
" ]\n"
")\n",
fv1, fv2, fv3, fv4,
pv1, pv2, pv3, pv4,
szFileVer,
szProdVer);
fclose(fp);
return TRUE;
}
// 向 .spec 文件注入或更新 version= 参数
// 若已有 version= 则更新其值,否则在 name= 参数后新增一行
static BOOL InjectVersionIntoSpec(LPCTSTR lpSpecPath)
{
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);
const std::string strNewValue = "version='version_info.txt'";
bool bFound = false;
std::string::size_type nPos = 0;
while ((nPos = strContent.find("version=", nPos)) != std::string::npos)
{
// 确保 version= 是独立参数(前驱字符为空白或换行,而非字母/数字)
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 nLineStart = strContent.rfind('\n', nPos);
nLineStart = (nLineStart == std::string::npos) ? 0 : nLineStart + 1;
std::string strIndent = strContent.substr(nLineStart, nPos - nLineStart);
// 获取行末
std::string::size_type nLineEnd = strContent.find('\n', nPos);
if (nLineEnd == std::string::npos) nLineEnd = strContent.size();
// 替换整行(保留缩进,统一加尾随逗号)
std::string strNewLine = strIndent + strNewValue + ",";
strContent.replace(nLineStart, nLineEnd - nLineStart, strNewLine);
_tprintf(_T("成功: 已更新 spec 文件 version 字段 -> version_info.txt\n"));
bFound = true;
break;
}
nPos += 8; // len("version=")
}
if (!bFound)
{
// 未找到 version=,在 name= 参数后插入新行
std::string::size_type nNamePos = strContent.find("name=");
if (nNamePos != std::string::npos)
{
// 获取 name= 行的缩进
std::string::size_type nLineStart = strContent.rfind('\n', nNamePos);
nLineStart = (nLineStart == std::string::npos) ? 0 : nLineStart + 1;
std::string strIndent = strContent.substr(nLineStart, nNamePos - nLineStart);
// 找到 name= 行末
std::string::size_type nLineEnd = strContent.find('\n', nNamePos);
if (nLineEnd == std::string::npos) nLineEnd = strContent.size() - 1;
// 在 name= 行后插入 version=
std::string strInsert = "\n" + strIndent + strNewValue + ",";
strContent.insert(nLineEnd, strInsert);
_tprintf(_T("成功: 已向 spec 文件新增 version 字段 -> version_info.txt\n"));
}
else
{
_tprintf(_T("警告: spec 文件中未找到 version= 或 name= 参数,跳过版本信息注入。\n"));
}
}
// 写回文件
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;
}
// 从 .spec 文件读取 version= 参数的值(单/双引号括起的路径)
// 返回 FALSE 表示未找到、值为 None 或值为空
static BOOL ReadVersionPathFromSpec(LPCTSTR lpSpecPath, CString& strVersionPath)
{
strVersionPath.Empty();
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; // skip "version="
while (nValStart < strContent.size() && strContent[nValStart] == ' ')
nValStart++;
if (nValStart >= strContent.size()) break;
char cQuote = strContent[nValStart];
if (cQuote == '\'' || cQuote == '"')
{
std::string::size_type nValEnd = strContent.find(cQuote, nValStart + 1);
if (nValEnd != std::string::npos && nValEnd > nValStart + 1)
{
strVersionPath = strContent.substr(nValStart + 1, nValEnd - nValStart - 1).c_str();
return TRUE;
}
}
break; // version=None 或空值,视为无
}
nPos += 8;
}
return FALSE;
}
// 更新已有 version_info 文件的版本字段;若文件不存在则创建
static BOOL UpdateVersionInfoFile(LPCTSTR lpPath, LPCTSTR lpProductVersion, LPCTSTR lpFileVersion)
{
UINT pv1 = 0, pv2 = 0, pv3 = 0, pv4 = 0;
UINT fv1 = 0, fv2 = 0, fv3 = 0, fv4 = 0;
if (!ParseVersionTuple(lpProductVersion, pv1, pv2, pv3, pv4)) return FALSE;
if (!ParseVersionTuple(lpFileVersion, fv1, fv2, fv3, fv4)) return FALSE;
CStringA strProdVerA(lpProductVersion);
CStringA strFileVerA(lpFileVersion);
const char* szProdVer = strProdVerA;
const char* szFileVer = strFileVerA;
if (!PathFileExists(lpPath))
{
// 文件不存在,直接创建
FILE* fp = NULL;
_tfopen_s(&fp, lpPath, _T("wb"));
if (!fp) return FALSE;
fprintf(fp,
"# UTF-8\n"
"VSVersionInfo(\n"
" ffi=FixedFileInfo(\n"
" filevers=(%u, %u, %u, %u),\n"
" prodvers=(%u, %u, %u, %u),\n"
" mask=0x3f,\n"
" flags=0x0,\n"
" OS=0x40004,\n"
" fileType=0x1,\n"
" subtype=0x0,\n"
" date=(0, 0)\n"
" ),\n"
" kids=[\n"
" StringFileInfo([\n"
" StringTable(\n"
" u'040904B0',\n"
" [StringStruct(u'CompanyName', u''),\n"
" StringStruct(u'FileDescription', u''),\n"
" StringStruct(u'FileVersion', u'%s'),\n"
" StringStruct(u'InternalName', u''),\n"
" StringStruct(u'LegalCopyright', u''),\n"
" StringStruct(u'OriginalFilename', u''),\n"
" StringStruct(u'ProductName', u''),\n"
" StringStruct(u'ProductVersion', u'%s'),\n"
" StringStruct(u'Comments', u''),\n"
" StringStruct(u'LegalTrademarks', u''),\n"
" ])\n"
" ]),\n"
" VarFileInfo([VarStruct(u'Translation', [0x0409, 1200])])\n"
" ]\n"
")\n",
fv1, fv2, fv3, fv4,
pv1, pv2, pv3, pv4,
szFileVer,
szProdVer);
fclose(fp);
return TRUE;
}
// 读取现有文件并就地更新版本字段
CFile myFile;
CFileException fileEx;
if (!myFile.Open(lpPath, CFile::modeReadWrite, &fileEx)) return FALSE;
DWORD dwLen = (DWORD)myFile.GetLength();
std::vector<char> buf(dwLen + 1, 0);
myFile.Read(buf.data(), dwLen);
myFile.Close();
std::string s(buf.data(), dwLen);
char szNew[128];
// 更新 filevers 元组
{
const std::string key = "filevers=(";
auto p = s.find(key);
if (p != std::string::npos) {
auto pValStart = p + key.size();
auto pValEnd = s.find(')', pValStart);
if (pValEnd != std::string::npos) {
snprintf(szNew, sizeof(szNew), "%u, %u, %u, %u", fv1, fv2, fv3, fv4);
s.replace(pValStart, pValEnd - pValStart, szNew);
}
}
}
// 更新 prodvers 元组
{
const std::string key = "prodvers=(";
auto p = s.find(key);
if (p != std::string::npos) {
auto pValStart = p + key.size();
auto pValEnd = s.find(')', pValStart);
if (pValEnd != std::string::npos) {
snprintf(szNew, sizeof(szNew), "%u, %u, %u, %u", pv1, pv2, pv3, pv4);
s.replace(pValStart, pValEnd - pValStart, szNew);
}
}
}
// 更新 FileVersion 字符串值
{
const std::string key = "u'FileVersion', u'";
auto p = s.find(key);
if (p != std::string::npos) {
auto pValStart = p + key.size();
auto pValEnd = s.find('\'', pValStart);
if (pValEnd != std::string::npos)
s.replace(pValStart, pValEnd - pValStart, szFileVer);
}
}
// 更新 ProductVersion 字符串值
{
const std::string key = "u'ProductVersion', u'";
auto p = s.find(key);
if (p != std::string::npos) {
auto pValStart = p + key.size();
auto pValEnd = s.find('\'', pValStart);
if (pValEnd != std::string::npos)
s.replace(pValStart, pValEnd - pValStart, szProdVer);
}
}
if (!myFile.Open(lpPath, CFile::modeCreate | CFile::modeWrite, &fileEx)) return FALSE;
myFile.Write(s.c_str(), (UINT)s.size());
myFile.Close();
return TRUE;
}
int HandlePyinstallerBuildCommand(int argc, TCHAR* argv[])
{
// 解析 pid内嵌在 argv[1] 中,如 "pyinstaller=5"
// "pyinstaller=" 为 13 个字符
CString strPid = CString(argv[1]).Mid(13);
UINT nPid = 0;
if (strPid.IsEmpty() || !TryParseUInt16(strPid, nPid))
{
_tprintf(_T("错误: pyinstaller= 后的 pid 无效:%s应为 0-65535 范围的整数。\n"), strPid.GetString());
return 41;
}
LPCTSTR lpRepoPath = NULL;
CString strExtraArgs;
BOOL bTestMode = FALSE;
int nRepoArgRet = ParsePyinstallerOptions(argc, argv, lpRepoPath, strExtraArgs, bTestMode);
if (nRepoArgRet != 0)
{
return nRepoArgRet;
}
CString strProductVersion;
CString strFileVersion;
VersionBuildErrorCodes errorCodes = { 42, 43, 44 };
int nVersionRet = BuildVersionsFromRepo(
lpRepoPath,
nPid,
errorCodes,
strProductVersion,
strFileVersion,
DEFAULT_MAJOR_WHEN_NO_TAG_FOR_PYINSTALLER,
DEFAULT_MINOR_WHEN_NO_TAG_FOR_PYINSTALLER);
if (nVersionRet != 0)
{
return nVersionRet;
}
if (bTestMode)
{
int nDot1 = strProductVersion.Find(_T('.'));
int nDot2 = (nDot1 >= 0) ? strProductVersion.Find(_T('.'), nDot1 + 1) : -1;
if (nDot2 > nDot1)
{
strProductVersion = strProductVersion.Left(nDot2 + 1) + _T("0.0");
}
_tprintf(_T("[test] 测试版本号: ProductVersion=%s\n"), strProductVersion.GetString());
}
// 检测是否为 spec 模式,若是则生成 version_info.txt 并注入/更新 spec
CString strFirstToken = ExtractFirstToken(strExtraArgs);
BOOL bIsSpec = (strFirstToken.GetLength() >= 5
&& strFirstToken.Right(5).CompareNoCase(_T(".spec")) == 0);
if (bIsSpec)
{
// 解析 spec 文件完整路径
CString strSpecPath;
if (PathIsRelative(strFirstToken))
strSpecPath.Format(_T("%s\\%s"), lpRepoPath, strFirstToken.GetString());
else
strSpecPath = strFirstToken;
// 获取 spec 所在目录
TCHAR szSpecDir[MAX_PATH] = {};
_tcscpy_s(szSpecDir, strSpecPath.GetString());
PathRemoveFileSpec(szSpecDir);
// 检查 spec 是否已指定版本信息文件
CString strExistingVersionPath;
if (ReadVersionPathFromSpec(strSpecPath, strExistingVersionPath))
{
// spec 已有 version=,直接更新所指向的文件(不存在则创建)
CString strVersionInfoFullPath;
if (PathIsRelative(strExistingVersionPath))
strVersionInfoFullPath.Format(_T("%s\\%s"), szSpecDir, strExistingVersionPath.GetString());
else
strVersionInfoFullPath = strExistingVersionPath;
if (!UpdateVersionInfoFile(strVersionInfoFullPath, strProductVersion, strFileVersion))
{
_tprintf(_T("错误: 无法更新版本信息文件: %s。\n"), strVersionInfoFullPath.GetString());
return 45;
}
_tprintf(_T("版本信息文件已更新: %s\n"), strVersionInfoFullPath.GetString());
}
else
{
// spec 未指定 version=,生成 version_info.txt 并注入 spec
CString strVersionInfoPath;
if (!WriteVersionInfoFile(szSpecDir, strProductVersion, strFileVersion, strVersionInfoPath))
{
_tprintf(_T("错误: 无法生成 version_info.txt目录: %s\n"), szSpecDir);
return 45;
}
_tprintf(_T("版本信息文件已生成: %s\n"), strVersionInfoPath.GetString());
if (!InjectVersionIntoSpec(strSpecPath))
{
_tprintf(_T("错误: 无法修改 spec 文件: %s。\n"), strSpecPath.GetString());
return 46;
}
}
}
_tprintf(_T("PyInstaller 打包开始..\n"));
_tprintf(_T("ProductVersion=%s\n"), strProductVersion.GetString());
_tprintf(_T("FileVersion=%s\n"), strFileVersion.GetString());
CString strCmd;
strCmd.Format(
_T("cmd /c python -m PyInstaller %s"),
strExtraArgs.GetString());
CString strBuildResult = StartProcess(NULL, strCmd.GetBuffer(), lpRepoPath);
strCmd.ReleaseBuffer();
if (strBuildResult.IsEmpty())
{
_tprintf(_T("错误: PyInstaller 打包失败。\n"));
}
return 0;
}

View File

@@ -0,0 +1,3 @@
#pragma once
int HandlePyinstallerBuildCommand(int argc, TCHAR* argv[]);

235
README.md
View File

@@ -10,8 +10,9 @@
gitver gitver
gitver rewrite [PEType] [-f] gitver rewrite [PEType] [-f]
gitver setver=<pid> [repodir=<path>] [-test] [-setup=0|1] gitver setver=<pid> [repodir=<path>] [-test] [-setup=0|1]
gitver nuitkabuild=<pid> <mainPy> [repodir=<path>] [-test] [params="<nuitka参数>"] gitver nuitkabuild=<pid> [repodir=<path>] [-test] [params="<mainPy> <nuitka参数>"]
gitver nuitkapydbuild=<pid> <modulePy> [repodir=<path>] [-test] [params="<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=0|1 <pid> [repodir=<path>] [-test]
``` ```
@@ -22,7 +23,7 @@ gitver -setup=0|1 <pid> [repodir=<path>] [-test]
| `pid` | 产品 ID整数范围 0-65535内嵌在命令名中`setver=5` | | `pid` | 产品 ID整数范围 0-65535内嵌在命令名中`setver=5` |
| `repodir=<path>` | Git 仓库目录,缺省使用当前运行目录 | | `repodir=<path>` | Git 仓库目录,缺省使用当前运行目录 |
| `-test` | 将产品版本号的 major/minor 置为 0测试版本构建 | | `-test` | 将产品版本号的 major/minor 置为 0测试版本构建 |
| `params="..."` | 传递给 Nuitka 的额外参数,用双引号括起来 | | `params="..."` | 传递给 Nuitka 或 PyInstaller 的参数(**含入口文件名**,用双引号括起来 |
**BID 规则:** **BID 规则:**
`main`/`master` 分支固定 `bid=0`;其他分支格式须为 `<描述>.<数字>`,自动读取末尾数字作为 `bid` `main`/`master` 分支固定 `bid=0`;其他分支格式须为 `<描述>.<数字>`,自动读取末尾数字作为 `bid`
@@ -145,21 +146,21 @@ FileVersion=5.26.0519.3
生成版本号并调用 Nuitka 打包 Python 程序EXE 模式)。 生成版本号并调用 Nuitka 打包 Python 程序EXE 模式)。
``` ```
gitver nuitkabuild=<pid> <mainPy> [repodir=<path>] [-test] [params="<nuitka参数>"] gitver nuitkabuild=<pid> [repodir=<path>] [-test] [params="<mainPy> <nuitka参数>"]
``` ```
自动调用: 自动调用:
``` ```
python -m nuitka --windows-product-version=<版本> --windows-file-version=<版本> <extra> <mainPy> python -m nuitka --windows-product-version=<版本> --windows-file-version=<版本> <params>
``` ```
**示例:** **示例:**
``` ```
gitver nuitkabuild=5 main.py gitver nuitkabuild=5 params="main.py"
gitver nuitkabuild=5 main.py -test gitver nuitkabuild=5 -test params="main.py"
gitver nuitkabuild=5 src\app.py repodir=E:\Code\MyPyProj params="--standalone --output-dir=dist" gitver nuitkabuild=5 repodir=E:\Code\MyPyProj params="src\\app.py --standalone --output-dir=dist"
``` ```
**输出示例:** **输出示例:**
@@ -179,27 +180,73 @@ FileVersion=5.26.0519.3
生成版本号并调用 Nuitka 打包 Python 模块pyd/DLL 模式)。 生成版本号并调用 Nuitka 打包 Python 模块pyd/DLL 模式)。
``` ```
gitver nuitkapydbuild=<pid> <modulePy> [repodir=<path>] [-test] [params="<nuitka参数>"] gitver nuitkapydbuild=<pid> [repodir=<path>] [-test] [params="<modulePy> <nuitka参数>"]
``` ```
自动调用: 自动调用:
``` ```
python -m nuitka --module --windows-product-version=<版本> --windows-file-version=<版本> <extra> <modulePy> python -m nuitka --module --windows-product-version=<版本> --windows-file-version=<版本> <params>
``` ```
**示例:** **示例:**
``` ```
gitver nuitkapydbuild=5 module.py gitver nuitkapydbuild=5 params="module.py"
gitver nuitkapydbuild=5 module.py -test gitver nuitkapydbuild=5 -test params="module.py"
gitver nuitkapydbuild=5 src\core.py repodir=E:\Code\MyPyProj params="--output-dir=dist" gitver nuitkapydbuild=5 repodir=E:\Code\MyPyProj params="src\\core.py --output-dir=dist"
``` ```
> 需要当前环境已安装 Nuitka可通过 `python -m nuitka --version` 验证)。 > 需要当前环境已安装 Nuitka可通过 `python -m nuitka --version` 验证)。
--- ---
### `gitver pyinstaller=<pid>`
生成版本号并调用 PyInstaller 打包 Python 程序,支持直接传入 `.py` 入口脚本或 `.spec` 构建配置文件。
```
gitver pyinstaller=<pid> [repodir=<path>] [-test] [params="<mainPy|specFile> <pyinstaller参数>"]
```
自动调用:
```
python -m PyInstaller <params>
```
版本号照常从 Git tag 计算并输出,但 PyInstaller 本身不接受 `--windows-product-version` 等命令行参数。如需将版本信息嵌入 EXE建议在 `.spec` 文件中通过 `version=` 字段或外部 version resource 文件实现。
**两种打包模式:**
| 模式 | 参数示例 | 说明 |
|---|---|---|
| 脚本模式 | `params="main.py"` | 由 PyInstaller 自动生成 spec 并打包 |
| spec 模式 | `params="myapp.spec"` | 使用已有 spec 文件,打包配置完全由 spec 控制,`params=` 仅适用于路径类运行时选项(`--distpath` 等) |
**示例:**
```
gitver pyinstaller=5 params="main.py"
gitver pyinstaller=5 -test params="main.py"
gitver pyinstaller=5 repodir=E:\Code\MyPyProj params="src\\app.py --onefile --name=MyApp"
gitver pyinstaller=5 params="myapp.spec"
gitver pyinstaller=5 -test params="myapp.spec"
gitver pyinstaller=5 repodir=E:\Code\MyPyProj params="myapp.spec"
```
**输出示例:**
```
PyInstaller 打包开始.. (main.py)
ProductVersion=5.0.1920.11
FileVersion=5.26.0519.3
```
> 需要当前环境已安装 PyInstaller可通过 `python -m PyInstaller --version` 验证)。
---
### `gitver -setup=0|1`(独立打包命令) ### `gitver -setup=0|1`(独立打包命令)
独立调用安装脚本打包,不依赖 `setver=` 命令。 独立调用安装脚本打包,不依赖 `setver=` 命令。
@@ -217,6 +264,112 @@ gitver -setup=1 5 repodir=E:\Code\MyProj
--- ---
## Nuitka 与 PyInstaller 常用参数速查
### Nuitka 常用参数
通过 `params="..."` 传递给 `gitver nuitkabuild=` / `nuitkapydbuild=`
| 参数 | 说明 |
|---|---|
| `--standalone` | 独立模式:将所有依赖打包进输出目录,可单独分发 |
| `--onefile` | 单文件模式:将独立包压缩为单个 EXE需配合 `--standalone` |
| `--output-dir=<dir>` | 指定输出目录,如 `--output-dir=dist` |
| `--windows-icon-from-ico=<ico>` | 设置 EXE 图标 |
| `--windows-company-name=<name>` | 设置公司名(写入文件属性) |
| `--windows-product-name=<name>` | 设置产品名 |
| `--enable-plugin=<plugin>` | 启用插件,如 `pyside6``tk-inter``numpy` |
| `--follow-imports` | 跟踪所有隐式导入(打包更完整,体积也更大) |
| `--nofollow-import-to=<mod>` | 排除指定模块不展开,如 `--nofollow-import-to=tests` |
| `--include-package=<pkg>` | 强制包含指定包 |
| `--include-data-files=<src>=<dst>` | 打包额外数据文件 |
| `--remove-output` | 打包前先清理上次输出 |
| `--lto=yes` | 启用链接时优化Release 构建推荐) |
| `--jobs=<n>` | 并行编译线程数,如 `--jobs=4` |
**典型用法示例:**
```bash
# 最小单文件 EXE
gitver nuitkabuild=5 params="main.py --standalone --onefile --output-dir=dist"
# 带图标、公司名的独立包
gitver nuitkabuild=5 params="main.py --standalone --windows-icon-from-ico=app.ico --windows-company-name=MyCompany --output-dir=dist"
# PySide6 GUI 应用
gitver nuitkabuild=5 params="main.py --standalone --enable-plugin=pyside6 --output-dir=dist"
# 打包 pyd 模块
gitver nuitkapydbuild=5 params="core.py --output-dir=dist"
# 异地仓库
gitver nuitkabuild=5 repodir=E:\Code\MyProj params="main.py --standalone --onefile --output-dir=dist"
```
---
### PyInstaller 常用参数
通过 `params="..."` 传递给 `gitver pyinstaller=`
| 参数 | 说明 |
|---|---|
| `--onefile` / `-F` | 单文件模式:打包成单个 EXE |
| `--onedir` / `-D` | 单目录模式(默认) |
| `--name=<name>` / `-n` | 指定输出文件名(不含后缀) |
| `--icon=<ico>` / `-i` | 设置 EXE 图标 |
| `--distpath=<dir>` | 指定输出目录,默认 `dist` |
| `--workpath=<dir>` | 中间文件目录,默认 `build` |
| `--specpath=<dir>` | spec 文件生成位置,默认当前目录 |
| `--noconsole` / `-w` | 不显示控制台窗口GUI 应用) |
| `--hidden-import=<mod>` | 手动指定隐式导入的模块 |
| `--add-data=<src>;<dst>` | 打包额外数据文件Windows 分隔符为 `;`Linux/macOS 为 `:` |
| `--version-file=<file>` | 从文件读入 Windows 版本信息嵌入 EXE |
| `--uac-admin` | 请求管理员权限运行 |
| `--clean` | 构建前清理缓存 |
| `--log-level=WARN` | 减少构建输出噪音,可选 `DEBUG``INFO``WARN``ERROR` |
**典型用法示例:**
```bash
# 最小单文件 EXE
gitver pyinstaller=5 params="main.py --onefile --name=MyApp"
# GUI 应用(关指控制台)
gitver pyinstaller=5 params="main.py --onefile --noconsole --icon=app.ico --name=MyApp"
# 包含资源文件
gitver pyinstaller=5 params="main.py --onefile --add-data=assets;assets --name=MyApp"
# 指定输出目录
gitver pyinstaller=5 params="main.py --onefile --distpath=dist --workpath=build --name=MyApp"
# 使用 spec 文件(配置均在 spec 中,不需要其他 params
gitver pyinstaller=5 params="myapp.spec"
# 使用 spec 并覆盖输出路径
gitver pyinstaller=5 params="myapp.spec --distpath=release"
# 异地仓库
gitver pyinstaller=5 repodir=E:\Code\MyProj params="main.py --onefile --name=MyApp"
```
**spec 文件嵌入版本信息(推荐方式):**
PyInstaller 不支持命令行版本参数。需将版本号嵌入 EXE 时,建议先用 `gitver setver=<pid>` 生成版本号,再在 spec 文件中设置 `version_file` 字段:
```python
# myapp.spec 片段
exe = EXE(
pyz,
...,
version='version_info.txt', # Windows 版本信息文件
name='MyApp',
)
```
---
## 错误码 ## 错误码
| 码 | 含义 | | 码 | 含义 |
@@ -249,6 +402,11 @@ gitver -setup=1 5 repodir=E:\Code\MyProj
| 37 | 未找到安装脚本setup.iss / setup.nsi | | 37 | 未找到安装脚本setup.iss / setup.nsi |
| 38 | 修改安装脚本失败 | | 38 | 修改安装脚本失败 |
| 39 | 未找到安装编译器ISCC.exe / makensis.exe | | 39 | 未找到安装编译器ISCC.exe / makensis.exe |
| 40 | `pyinstaller=` 参数不足(缺少 mainPy 或 specFile |
| 41 | `pyinstaller=` 的 pid 非法 |
| 42 | `pyinstaller=` 无法根据分支名计算 bid |
| 43 | `pyinstaller=` 读取 tag 失败 |
| 44 | `pyinstaller=` 获取当天分支提交次数失败 |
--- ---
@@ -304,13 +462,27 @@ gitver setver=5 -test
### 验证 `nuitkabuild=` ### 验证 `nuitkabuild=`
```bash ```bash
gitver nuitkabuild=5 main.py gitver nuitkabuild=5 params="main.py"
# 预期: 调用 python -m nuitka注入版本号参数 # 预期: 调用 python -m nuitka注入版本号参数
gitver nuitkabuild=5 src\app.py params="--standalone --output-dir=dist" gitver nuitkabuild=5 params="src\\app.py --standalone --output-dir=dist"
# 预期: 额外参数传递给 Nuitka # 预期: 额外参数传递给 Nuitka
``` ```
### 验证 `pyinstaller=`
```bash
gitver pyinstaller=5 params="main.py"
# 预期: 调用 python -m PyInstaller main.py
# 输出 ProductVersion / FileVersion
gitver pyinstaller=5 params="myapp.spec"
# 预期: 调用 python -m PyInstaller myapp.specspec 模式)
gitver pyinstaller=5 params="main.py --onefile --name=MyApp"
# 预期: 额外参数传递给 PyInstaller
```
### 验证 `-setup=` ### 验证 `-setup=`
```bash ```bash
@@ -347,21 +519,34 @@ gitver setver=5 -setup=0
| 命令 | 预期返回码 | | 命令 | 预期返回码 |
|---|---| |---|---|
| `gitver nuitkabuild=5 main.py` | 0 / 19 / 21 | | `gitver nuitkabuild=5 params="main.py"` | 0 / 19 / 21 |
| `gitver nuitkabuild=5 main.py -test` | 0 | | `gitver nuitkabuild=5 -test params="main.py"` | 0 |
| `gitver nuitkabuild=5 main.py params="--standalone"` | 0 | | `gitver nuitkabuild=5 params="main.py --standalone"` | 0 |
| `gitver nuitkabuild=5 main.py repodir=E:\NotExists` | 23 | | `gitver nuitkabuild=5 repodir=E:\NotExists params="main.py"` | 23 |
| `gitver nuitkabuild=5` | 17 | | `gitver nuitkabuild=5` | 0 |
### nuitkapydbuild= 组 ### nuitkapydbuild= 组
| 命令 | 预期返回码 | | 命令 | 预期返回码 |
|---|---| |---|---|
| `gitver nuitkapydbuild=5 module.py` | 0 / 26 / 28 | | `gitver nuitkapydbuild=5 params="module.py"` | 0 / 26 / 28 |
| `gitver nuitkapydbuild=5 module.py -test` | 0 | | `gitver nuitkapydbuild=5 -test params="module.py"` | 0 |
| `gitver nuitkapydbuild=5 module.py params="--output-dir=dist"` | 0 | | `gitver nuitkapydbuild=5 params="module.py --output-dir=dist"` | 0 |
| `gitver nuitkapydbuild=5 module.py repodir=E:\NotExists` | 23 | | `gitver nuitkapydbuild=5 repodir=E:\NotExists params="module.py"` | 23 |
| `gitver nuitkapydbuild=5` | 24 | | `gitver nuitkapydbuild=5` | 0 |
### pyinstaller= 组
| 命令 | 预期返回码 |
|---|---|
| `gitver pyinstaller=5 params="main.py"` | 0 / 42 / 44 |
| `gitver pyinstaller=5 -test params="main.py"` | 0 |
| `gitver pyinstaller=5 params="main.py --onefile"` | 0 |
| `gitver pyinstaller=5 params="myapp.spec"` | 0 |
| `gitver pyinstaller=5 -test params="myapp.spec"` | 0 |
| `gitver pyinstaller=5 repodir=E:\NotExists params="main.py"` | 23 |
| `gitver pyinstaller=5` | 0 |
| `gitver pyinstaller=abc params="main.py"` | 41 |
> **注意**如果打包时提示“未找到安装编译器ISCC.exe / makensis.exe请手动下载安装对应工具并将其可执行文件路径如 `ISCC.exe` 或 `makensis.exe` 所在目录)添加到系统环境变量 `PATH`。常见下载地址: > **注意**如果打包时提示“未找到安装编译器ISCC.exe / makensis.exe请手动下载安装对应工具并将其可执行文件路径如 `ISCC.exe` 或 `makensis.exe` 所在目录)添加到系统环境变量 `PATH`。常见下载地址:
> >