From 150486a29b89371d62f0bb12052d9a93b76888d7 Mon Sep 17 00:00:00 2001 From: Jeff <632006142@qq.com> Date: Wed, 20 May 2026 19:41:31 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dpyinstaller=E6=89=93=E5=8C=85?= =?UTF-8?q?=E5=A4=B1=E8=B4=A5=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- GitVer/GitVer_pyinstaller.cpp | 115 +++++++++++++++++----------------- README.md | 93 +++++++++++++++++++++++++++ 2 files changed, 151 insertions(+), 57 deletions(-) diff --git a/GitVer/GitVer_pyinstaller.cpp b/GitVer/GitVer_pyinstaller.cpp index 1570135..d0b45e8 100644 --- a/GitVer/GitVer_pyinstaller.cpp +++ b/GitVer/GitVer_pyinstaller.cpp @@ -31,7 +31,7 @@ static int ParsePyinstallerOptions(int argc, TCHAR* argv[], LPCTSTR& lpRepoPath, { if (lpRepoPath != NULL) { - _tprintf(_T("错误: repodir 参数重复: %s\n"), argv[i]); + _tprintf(_T(": repodir ظ: %s\n"), argv[i]); return 23; } LPCTSTR lpPath = argv[i] + 8; @@ -48,18 +48,18 @@ static int ParsePyinstallerOptions(int argc, TCHAR* argv[], LPCTSTR& lpRepoPath, { if (!strExtraArgs.IsEmpty()) { - _tprintf(_T("错误: params 参数重复。\n")); + _tprintf(_T(": params ظ\n")); return 3; } CString strVal = strArg.Mid(7); - // 去掉外层双引号(如 params="--onefile") + // ȥ˫ţ 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]); + _tprintf(_T(": δ֪IJ %s\n"), argv[i]); return 3; } @@ -71,7 +71,7 @@ static int ParsePyinstallerOptions(int argc, TCHAR* argv[], LPCTSTR& lpRepoPath, return 0; } -// 解析版本字符串 "a.b.c.d" → 四个分量 +// 汾ַ "a.b.c.d" ĸ static BOOL ParseVersionTuple(LPCTSTR lpVer, UINT& v1, UINT& v2, UINT& v3, UINT& v4) { CString str(lpVer); @@ -88,7 +88,7 @@ static BOOL ParseVersionTuple(LPCTSTR lpVer, UINT& v1, UINT& v2, UINT& v3, UINT& return TRUE; } -// 提取 params 中第一个空格分隔的 token(支持双引号括起的路径) +// ȡ params еһոָ token֧˫· static CString ExtractFirstToken(const CString& strParams) { CString str = strParams; @@ -105,7 +105,7 @@ static CString ExtractFirstToken(const CString& strParams) return str.Left(nSpace); } -// 生成 version_info.txt 到 lpDir 目录(PyInstaller VSVersionInfo 格式) +// 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; @@ -165,13 +165,13 @@ static BOOL WriteVersionInfoFile(LPCTSTR lpDir, LPCTSTR lpProductVersion, LPCTST return TRUE; } -// 向 .spec 文件注入或更新 version= 参数 -// 若已有 version= 则更新其值,否则在 name= 参数后新增一行 +// .spec ļע version= +// version= ֵ name= һ static BOOL InjectVersionIntoSpec(LPCTSTR lpSpecPath) { if (!PathFileExists(lpSpecPath)) { - _tprintf(_T("错误: spec 文件不存在 %s\n"), lpSpecPath); + _tprintf(_T(": spec ļ %s\n"), lpSpecPath); return FALSE; } @@ -179,7 +179,7 @@ static BOOL InjectVersionIntoSpec(LPCTSTR lpSpecPath) CFileException fileEx; if (!myFile.Open(lpSpecPath, CFile::modeReadWrite, &fileEx)) { - _tprintf(_T("错误: 无法打开 spec 文件 %s\n"), lpSpecPath); + _tprintf(_T(": ޷ spec ļ %s\n"), lpSpecPath); return FALSE; } @@ -195,7 +195,7 @@ static BOOL InjectVersionIntoSpec(LPCTSTR lpSpecPath) std::string::size_type nPos = 0; while ((nPos = strContent.find("version=", nPos)) != std::string::npos) { - // 确保 version= 是独立参数(前驱字符为空白或换行,而非字母/数字) + // ȷ version= ǶǰַΪհ׻Уĸ/֣ bool bIsParam = (nPos == 0 || strContent[nPos - 1] == '\n' || strContent[nPos - 1] == '\r' @@ -203,19 +203,19 @@ static BOOL InjectVersionIntoSpec(LPCTSTR lpSpecPath) || 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")); + _tprintf(_T("ɹ: Ѹ spec ļ version ֶ -> version_info.txt\n")); bFound = true; break; } @@ -224,34 +224,34 @@ static BOOL InjectVersionIntoSpec(LPCTSTR lpSpecPath) if (!bFound) { - // 未找到 version=,在 name= 参数后插入新行 + // δҵ version= name= std::string::size_type nNamePos = strContent.find("name="); if (nNamePos != std::string::npos) { - // 获取 name= 行的缩进 + // ȡ 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= 行末 + // ҵ name= ĩ std::string::size_type nLineEnd = strContent.find('\n', nNamePos); if (nLineEnd == std::string::npos) nLineEnd = strContent.size() - 1; - // 在 name= 行后插入 version= + // name= к version= std::string strInsert = "\n" + strIndent + strNewValue + ","; strContent.insert(nLineEnd, strInsert); - _tprintf(_T("成功: 已向 spec 文件新增 version 字段 -> version_info.txt\n")); + _tprintf(_T("ɹ: spec ļ version ֶ -> version_info.txt\n")); } else { - _tprintf(_T("警告: spec 文件中未找到 version= 或 name= 参数,跳过版本信息注入。\n")); + _tprintf(_T(": spec ļδҵ version= name= 汾Ϣע롣\n")); } } - // 写回文件 + // дļ if (!myFile.Open(lpSpecPath, CFile::modeCreate | CFile::modeWrite, &fileEx)) { - _tprintf(_T("错误: 无法写入 spec 文件 %s\n"), lpSpecPath); + _tprintf(_T(": ޷д spec ļ %s\n"), lpSpecPath); return FALSE; } myFile.Write(strContent.c_str(), (UINT)strContent.size()); @@ -260,8 +260,8 @@ static BOOL InjectVersionIntoSpec(LPCTSTR lpSpecPath) return TRUE; } -// 从 .spec 文件读取 version= 参数的值(单/双引号括起的路径) -// 返回 FALSE 表示未找到、值为 None 或值为空 +// .spec ļȡ version= ֵ/˫· +// FALSE ʾδҵֵΪ None ֵΪ static BOOL ReadVersionPathFromSpec(LPCTSTR lpSpecPath, CString& strVersionPath) { strVersionPath.Empty(); @@ -303,14 +303,14 @@ static BOOL ReadVersionPathFromSpec(LPCTSTR lpSpecPath, CString& strVersionPath) return TRUE; } } - break; // version=None 或空值,视为无 + break; // version=None ֵΪ } nPos += 8; } return FALSE; } -// 更新已有 version_info 文件的版本字段;若文件不存在则创建 +// version_info ļİ汾ֶΣļ򴴽 static BOOL UpdateVersionInfoFile(LPCTSTR lpPath, LPCTSTR lpProductVersion, LPCTSTR lpFileVersion) { UINT pv1 = 0, pv2 = 0, pv3 = 0, pv4 = 0; @@ -325,7 +325,7 @@ static BOOL UpdateVersionInfoFile(LPCTSTR lpPath, LPCTSTR lpProductVersion, LPCT if (!PathFileExists(lpPath)) { - // 文件不存在,直接创建 + // ļڣֱӴ FILE* fp = NULL; _tfopen_s(&fp, lpPath, _T("wb")); if (!fp) return FALSE; @@ -369,7 +369,7 @@ static BOOL UpdateVersionInfoFile(LPCTSTR lpPath, LPCTSTR lpProductVersion, LPCT return TRUE; } - // 读取现有文件并就地更新版本字段 + // ȡļ͵ظ°汾ֶ CFile myFile; CFileException fileEx; if (!myFile.Open(lpPath, CFile::modeReadWrite, &fileEx)) return FALSE; @@ -381,7 +381,7 @@ static BOOL UpdateVersionInfoFile(LPCTSTR lpPath, LPCTSTR lpProductVersion, LPCT std::string s(buf.data(), dwLen); char szNew[128]; - // 更新 filevers 元组 + // filevers Ԫ { const std::string key = "filevers=("; auto p = s.find(key); @@ -394,7 +394,7 @@ static BOOL UpdateVersionInfoFile(LPCTSTR lpPath, LPCTSTR lpProductVersion, LPCT } } } - // 更新 prodvers 元组 + // prodvers Ԫ { const std::string key = "prodvers=("; auto p = s.find(key); @@ -407,7 +407,7 @@ static BOOL UpdateVersionInfoFile(LPCTSTR lpPath, LPCTSTR lpProductVersion, LPCT } } } - // 更新 FileVersion 字符串值 + // FileVersion ֵַ { const std::string key = "u'FileVersion', u'"; auto p = s.find(key); @@ -418,7 +418,7 @@ static BOOL UpdateVersionInfoFile(LPCTSTR lpPath, LPCTSTR lpProductVersion, LPCT s.replace(pValStart, pValEnd - pValStart, szFileVer); } } - // 更新 ProductVersion 字符串值 + // ProductVersion ֵַ { const std::string key = "u'ProductVersion', u'"; auto p = s.find(key); @@ -438,13 +438,16 @@ static BOOL UpdateVersionInfoFile(LPCTSTR lpPath, LPCTSTR lpProductVersion, LPCT int HandlePyinstallerBuildCommand(int argc, TCHAR* argv[]) { - // 解析 pid(内嵌在 argv[1] 中,如 "pyinstaller=5") - // "pyinstaller=" 为 13 个字符 - CString strPid = CString(argv[1]).Mid(13); +#ifdef _DEBUG + Sleep(15000); +#endif + // pidǶ argv[1] У "pyinstaller=5" + // "pyinstaller=" Ϊ 12 ַ UINT nPid = 0; + CString strPid = CString(argv[1]).Mid(12); if (strPid.IsEmpty() || !TryParseUInt16(strPid, nPid)) { - _tprintf(_T("错误: pyinstaller= 后的 pid 无效:%s,应为 0-65535 范围的整数。\n"), strPid.GetString()); + _tprintf(_T(": pyinstaller= pid Ч%sӦΪ 0-65535 Χ\n"), strPid.GetString()); return 41; } @@ -468,6 +471,7 @@ int HandlePyinstallerBuildCommand(int argc, TCHAR* argv[]) strFileVersion, DEFAULT_MAJOR_WHEN_NO_TAG_FOR_PYINSTALLER, DEFAULT_MINOR_WHEN_NO_TAG_FOR_PYINSTALLER); + if (nVersionRet != 0) { return nVersionRet; @@ -481,32 +485,31 @@ int HandlePyinstallerBuildCommand(int argc, TCHAR* argv[]) { strProductVersion = strProductVersion.Left(nDot2 + 1) + _T("0.0"); } - _tprintf(_T("[test] 测试版本号: ProductVersion=%s\n"), strProductVersion.GetString()); + _tprintf(_T("[test] ԰汾: ProductVersion=%s\n"), strProductVersion.GetString()); } - // 检测是否为 spec 模式,若是则生成 version_info.txt 并注入/更新 spec + // ǷΪ spec ģʽ version_info.txt ע/ spec CString strFirstToken = ExtractFirstToken(strExtraArgs); - BOOL bIsSpec = (strFirstToken.GetLength() >= 5 - && strFirstToken.Right(5).CompareNoCase(_T(".spec")) == 0); + BOOL bIsSpec = (strFirstToken.GetLength() >= 5 && strFirstToken.Right(5).CompareNoCase(_T(".spec")) == 0); if (bIsSpec) { - // 解析 spec 文件完整路径 + // spec ļ· CString strSpecPath; if (PathIsRelative(strFirstToken)) strSpecPath.Format(_T("%s\\%s"), lpRepoPath, strFirstToken.GetString()); else strSpecPath = strFirstToken; - // 获取 spec 所在目录 + // ȡ spec Ŀ¼ TCHAR szSpecDir[MAX_PATH] = {}; _tcscpy_s(szSpecDir, strSpecPath.GetString()); PathRemoveFileSpec(szSpecDir); - // 检查 spec 是否已指定版本信息文件 + // spec Ƿָ汾Ϣļ CString strExistingVersionPath; if (ReadVersionPathFromSpec(strSpecPath, strExistingVersionPath)) { - // spec 已有 version=,直接更新所指向的文件(不存在则创建) + // spec version=ֱӸָļ򴴽 CString strVersionInfoFullPath; if (PathIsRelative(strExistingVersionPath)) strVersionInfoFullPath.Format(_T("%s\\%s"), szSpecDir, strExistingVersionPath.GetString()); @@ -515,45 +518,43 @@ int HandlePyinstallerBuildCommand(int argc, TCHAR* argv[]) if (!UpdateVersionInfoFile(strVersionInfoFullPath, strProductVersion, strFileVersion)) { - _tprintf(_T("错误: 无法更新版本信息文件: %s。\n"), strVersionInfoFullPath.GetString()); + _tprintf(_T(": ޷°汾Ϣļ: %s\n"), strVersionInfoFullPath.GetString()); return 45; } - _tprintf(_T("版本信息文件已更新: %s\n"), strVersionInfoFullPath.GetString()); + _tprintf(_T("汾ϢļѸ: %s\n"), strVersionInfoFullPath.GetString()); } else { - // spec 未指定 version=,生成 version_info.txt 并注入 spec + // spec δָ version= version_info.txt ע spec CString strVersionInfoPath; if (!WriteVersionInfoFile(szSpecDir, strProductVersion, strFileVersion, strVersionInfoPath)) { - _tprintf(_T("错误: 无法生成 version_info.txt(目录: %s)。\n"), szSpecDir); + _tprintf(_T(": ޷ version_info.txtĿ¼: %s\n"), szSpecDir); return 45; } - _tprintf(_T("版本信息文件已生成: %s\n"), strVersionInfoPath.GetString()); + _tprintf(_T("汾Ϣļ: %s\n"), strVersionInfoPath.GetString()); if (!InjectVersionIntoSpec(strSpecPath)) { - _tprintf(_T("错误: 无法修改 spec 文件: %s。\n"), strSpecPath.GetString()); + _tprintf(_T(": ޷޸ spec ļ: %s\n"), strSpecPath.GetString()); return 46; } } } - _tprintf(_T("PyInstaller 打包开始..\n")); + _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()); + 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")); + _tprintf(_T(": PyInstaller ʧܡ\n")); } return 0; diff --git a/README.md b/README.md index 67bf7de..c680f79 100644 --- a/README.md +++ b/README.md @@ -368,6 +368,99 @@ exe = EXE( ) ``` +**完整 spec 文件示例(`myapp.spec`):** + +```python +# -*- mode: python ; coding: utf-8 -*- + +block_cipher = None + +a = Analysis( + ['src\\main.py'], # 入口脚本 + pathex=[], + binaries=[], + datas=[ + ('assets', 'assets'), # 额外资源目录:源路径, 目标路径 + ], + hiddenimports=[], + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher, + noarchive=False, +) + +pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) + +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.zipfiles, + a.datas, + [], + name='MyApp', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=False, # True=控制台程序,False=GUI程序 + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, + icon='assets\\app.ico', # 图标文件路径 + version='version_info.txt', # Windows 版本信息文件 +) +``` + +**配套的 `version_info.txt` 示例:** + +```python +# UTF-8 +# Windows EXE 版本信息文件 +# 版本号格式:major, minor, patch, build(均为整数) + +VSVersionInfo( + ffi=FixedFileInfo( + filevers=(5, 0, 1920, 11), # FileVersion,对应 FileVersion=5.0.1920.11 + prodvers=(5, 0, 1920, 11), # ProductVersion + mask=0x3f, + flags=0x0, + OS=0x40004, + fileType=0x1, # 0x1=EXE, 0x2=DLL + subtype=0x0, + date=(0, 0), + ), + kids=[ + StringFileInfo([ + StringTable( + '040904B0', # 语言 0409=英语, 04B0=Unicode + [ + StringStruct('CompanyName', 'MyCompany'), + StringStruct('FileDescription', 'MyApp Application'), + StringStruct('FileVersion', '5.0.1920.11'), + StringStruct('InternalName', 'MyApp'), + StringStruct('LegalCopyright', 'Copyright (C) 2026 MyCompany'), + StringStruct('OriginalFilename', 'MyApp.exe'), + StringStruct('ProductName', 'MyApp'), + StringStruct('ProductVersion', '5.0.1920.11'), + ] + ) + ]), + VarFileInfo([VarStruct('Translation', [0x0409, 0x04B0])]) + ] +) +``` + +> `version_info.txt` 中的版本号需与实际构建版本保持一致。如需自动同步,可在构建脚本中先调用 `gitver setver=` 获取版本,再用脚本替换 `version_info.txt` 中的版本字段,然后执行 `gitver pyinstaller= params="myapp.spec"`。 + --- ## 错误码