修复pyinstaller打包失败的问题

This commit is contained in:
2026-05-20 19:41:31 +08:00
parent d36618d101
commit 150486a29b
2 changed files with 151 additions and 57 deletions

View File

@@ -31,7 +31,7 @@ static int ParsePyinstallerOptions(int argc, TCHAR* argv[], LPCTSTR& lpRepoPath,
{ {
if (lpRepoPath != NULL) if (lpRepoPath != NULL)
{ {
_tprintf(_T("错误: repodir 参数重复: %s\n"), argv[i]); _tprintf(_T("错误: repodir 参数重复: %s\n"), argv[i]);
return 23; return 23;
} }
LPCTSTR lpPath = argv[i] + 8; LPCTSTR lpPath = argv[i] + 8;
@@ -48,18 +48,18 @@ static int ParsePyinstallerOptions(int argc, TCHAR* argv[], LPCTSTR& lpRepoPath,
{ {
if (!strExtraArgs.IsEmpty()) if (!strExtraArgs.IsEmpty())
{ {
_tprintf(_T("错误: params 参数重复。\n")); _tprintf(_T("错误: params 参数重复。\n"));
return 3; return 3;
} }
CString strVal = strArg.Mid(7); CString strVal = strArg.Mid(7);
// 去掉外层双引号(如 params="--onefile" // 去掉外层双引号(如 params="--onefile"
if (strVal.GetLength() >= 2 && strVal[0] == _T('"') && strVal[strVal.GetLength() - 1] == _T('"')) if (strVal.GetLength() >= 2 && strVal[0] == _T('"') && strVal[strVal.GetLength() - 1] == _T('"'))
strVal = strVal.Mid(1, strVal.GetLength() - 2); strVal = strVal.Mid(1, strVal.GetLength() - 2);
strExtraArgs = strVal; strExtraArgs = strVal;
continue; continue;
} }
_tprintf(_T("错误: 未知的参数 %s\n"), argv[i]); _tprintf(_T("错误: 未知的参数 %s\n"), argv[i]);
return 3; return 3;
} }
@@ -71,7 +71,7 @@ static int ParsePyinstallerOptions(int argc, TCHAR* argv[], LPCTSTR& lpRepoPath,
return 0; return 0;
} }
// 解析版本字符串 "a.b.c.d" → 四个分量 // 解析版本字符串 "a.b.c.d" → 四个分量
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 +88,7 @@ static BOOL ParseVersionTuple(LPCTSTR lpVer, UINT& v1, UINT& v2, UINT& v3, UINT&
return TRUE; return TRUE;
} }
// 提取 params 中第一个空格分隔的 token支持双引号括起的路径 // 提取 params 中第一个空格分隔的 token支持双引号括起的路径
static CString ExtractFirstToken(const CString& strParams) static CString ExtractFirstToken(const CString& strParams)
{ {
CString str = strParams; CString str = strParams;
@@ -105,7 +105,7 @@ static CString ExtractFirstToken(const CString& strParams)
return str.Left(nSpace); 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) 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,13 +165,13 @@ static BOOL WriteVersionInfoFile(LPCTSTR lpDir, LPCTSTR lpProductVersion, LPCTST
return TRUE; return TRUE;
} }
// 向 .spec 文件注入或更新 version= 参数 // 向 .spec 文件注入或更新 version= 参数
// 若已有 version= 则更新其值,否则在 name= 参数后新增一行 // 若已有 version= 则更新其值,否则在 name= 参数后新增一行
static BOOL InjectVersionIntoSpec(LPCTSTR lpSpecPath) static BOOL InjectVersionIntoSpec(LPCTSTR lpSpecPath)
{ {
if (!PathFileExists(lpSpecPath)) if (!PathFileExists(lpSpecPath))
{ {
_tprintf(_T("错误: spec 文件不存在 %s\n"), lpSpecPath); _tprintf(_T("错误: spec 文件不存在 %s\n"), lpSpecPath);
return FALSE; return FALSE;
} }
@@ -179,7 +179,7 @@ static BOOL InjectVersionIntoSpec(LPCTSTR lpSpecPath)
CFileException fileEx; CFileException fileEx;
if (!myFile.Open(lpSpecPath, CFile::modeReadWrite, &fileEx)) if (!myFile.Open(lpSpecPath, CFile::modeReadWrite, &fileEx))
{ {
_tprintf(_T("错误: 无法打开 spec 文件 %s\n"), lpSpecPath); _tprintf(_T("错误: 无法打开 spec 文件 %s\n"), lpSpecPath);
return FALSE; return FALSE;
} }
@@ -195,7 +195,7 @@ static BOOL InjectVersionIntoSpec(LPCTSTR lpSpecPath)
std::string::size_type nPos = 0; std::string::size_type nPos = 0;
while ((nPos = strContent.find("version=", nPos)) != std::string::npos) while ((nPos = strContent.find("version=", nPos)) != std::string::npos)
{ {
// 确保 version= 是独立参数(前驱字符为空白或换行,而非字母/数字) // 确保 version= 是独立参数(前驱字符为空白或换行,而非字母/数字)
bool bIsParam = (nPos == 0 bool bIsParam = (nPos == 0
|| strContent[nPos - 1] == '\n' || strContent[nPos - 1] == '\n'
|| strContent[nPos - 1] == '\r' || strContent[nPos - 1] == '\r'
@@ -203,19 +203,19 @@ static BOOL InjectVersionIntoSpec(LPCTSTR lpSpecPath)
|| strContent[nPos - 1] == '\t'); || strContent[nPos - 1] == '\t');
if (bIsParam) if (bIsParam)
{ {
// 获取行首(保留缩进) // 获取行首(保留缩进)
std::string::size_type nLineStart = strContent.rfind('\n', nPos); std::string::size_type nLineStart = strContent.rfind('\n', nPos);
nLineStart = (nLineStart == std::string::npos) ? 0 : nLineStart + 1; nLineStart = (nLineStart == std::string::npos) ? 0 : nLineStart + 1;
std::string strIndent = strContent.substr(nLineStart, nPos - nLineStart); std::string strIndent = strContent.substr(nLineStart, nPos - nLineStart);
// 获取行末 // 获取行末
std::string::size_type nLineEnd = strContent.find('\n', nPos); std::string::size_type nLineEnd = strContent.find('\n', nPos);
if (nLineEnd == std::string::npos) nLineEnd = strContent.size(); if (nLineEnd == std::string::npos) nLineEnd = strContent.size();
// 替换整行(保留缩进,统一加尾随逗号) // 替换整行(保留缩进,统一加尾随逗号)
std::string strNewLine = strIndent + strNewValue + ","; std::string strNewLine = strIndent + strNewValue + ",";
strContent.replace(nLineStart, nLineEnd - nLineStart, strNewLine); strContent.replace(nLineStart, nLineEnd - nLineStart, strNewLine);
_tprintf(_T("成功: 已更新 spec 文件 version 字段 -> version_info.txt\n")); _tprintf(_T("成功: 已更新 spec 文件 version 字段 -> version_info.txt\n"));
bFound = true; bFound = true;
break; break;
} }
@@ -224,34 +224,34 @@ static BOOL InjectVersionIntoSpec(LPCTSTR lpSpecPath)
if (!bFound) if (!bFound)
{ {
// 未找到 version=,在 name= 参数后插入新行 // 未找到 version=,在 name= 参数后插入新行
std::string::size_type nNamePos = strContent.find("name="); std::string::size_type nNamePos = strContent.find("name=");
if (nNamePos != std::string::npos) if (nNamePos != std::string::npos)
{ {
// 获取 name= 行的缩进 // 获取 name= 行的缩进
std::string::size_type nLineStart = strContent.rfind('\n', nNamePos); std::string::size_type nLineStart = strContent.rfind('\n', nNamePos);
nLineStart = (nLineStart == std::string::npos) ? 0 : nLineStart + 1; nLineStart = (nLineStart == std::string::npos) ? 0 : nLineStart + 1;
std::string strIndent = strContent.substr(nLineStart, nNamePos - nLineStart); std::string strIndent = strContent.substr(nLineStart, nNamePos - nLineStart);
// 找到 name= 行末 // 找到 name= 行末
std::string::size_type nLineEnd = strContent.find('\n', nNamePos); std::string::size_type nLineEnd = strContent.find('\n', nNamePos);
if (nLineEnd == std::string::npos) nLineEnd = strContent.size() - 1; if (nLineEnd == std::string::npos) nLineEnd = strContent.size() - 1;
// 在 name= 行后插入 version= // 在 name= 行后插入 version=
std::string strInsert = "\n" + strIndent + strNewValue + ","; std::string strInsert = "\n" + strIndent + strNewValue + ",";
strContent.insert(nLineEnd, strInsert); strContent.insert(nLineEnd, strInsert);
_tprintf(_T("成功: 已向 spec 文件新增 version 字段 -> version_info.txt\n")); _tprintf(_T("成功: 已向 spec 文件新增 version 字段 -> version_info.txt\n"));
} }
else else
{ {
_tprintf(_T("警告: spec 文件中未找到 version= 或 name= 参数,跳过版本信息注入。\n")); _tprintf(_T("警告: spec 文件中未找到 version= 或 name= 参数,跳过版本信息注入。\n"));
} }
} }
// 写回文件 // 写回文件
if (!myFile.Open(lpSpecPath, CFile::modeCreate | CFile::modeWrite, &fileEx)) if (!myFile.Open(lpSpecPath, CFile::modeCreate | CFile::modeWrite, &fileEx))
{ {
_tprintf(_T("错误: 无法写入 spec 文件 %s\n"), lpSpecPath); _tprintf(_T("错误: 无法写入 spec 文件 %s\n"), lpSpecPath);
return FALSE; return FALSE;
} }
myFile.Write(strContent.c_str(), (UINT)strContent.size()); myFile.Write(strContent.c_str(), (UINT)strContent.size());
@@ -260,8 +260,8 @@ static BOOL InjectVersionIntoSpec(LPCTSTR lpSpecPath)
return TRUE; return TRUE;
} }
// 从 .spec 文件读取 version= 参数的值(单/双引号括起的路径) // 从 .spec 文件读取 version= 参数的值(单/双引号括起的路径)
// 返回 FALSE 表示未找到、值为 None 或值为空 // 返回 FALSE 表示未找到、值为 None 或值为空
static BOOL ReadVersionPathFromSpec(LPCTSTR lpSpecPath, CString& strVersionPath) static BOOL ReadVersionPathFromSpec(LPCTSTR lpSpecPath, CString& strVersionPath)
{ {
strVersionPath.Empty(); strVersionPath.Empty();
@@ -303,14 +303,14 @@ static BOOL ReadVersionPathFromSpec(LPCTSTR lpSpecPath, CString& strVersionPath)
return TRUE; return TRUE;
} }
} }
break; // version=None 或空值,视为无 break; // version=None 或空值,视为无
} }
nPos += 8; nPos += 8;
} }
return FALSE; return FALSE;
} }
// 更新已有 version_info 文件的版本字段;若文件不存在则创建 // 更新已有 version_info 文件的版本字段;若文件不存在则创建
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;
@@ -325,7 +325,7 @@ static BOOL UpdateVersionInfoFile(LPCTSTR lpPath, LPCTSTR lpProductVersion, LPCT
if (!PathFileExists(lpPath)) if (!PathFileExists(lpPath))
{ {
// 文件不存在,直接创建 // 文件不存在,直接创建
FILE* fp = NULL; FILE* fp = NULL;
_tfopen_s(&fp, lpPath, _T("wb")); _tfopen_s(&fp, lpPath, _T("wb"));
if (!fp) return FALSE; if (!fp) return FALSE;
@@ -369,7 +369,7 @@ static BOOL UpdateVersionInfoFile(LPCTSTR lpPath, LPCTSTR lpProductVersion, LPCT
return TRUE; return TRUE;
} }
// 读取现有文件并就地更新版本字段 // 读取现有文件并就地更新版本字段
CFile myFile; CFile myFile;
CFileException fileEx; CFileException fileEx;
if (!myFile.Open(lpPath, CFile::modeReadWrite, &fileEx)) return FALSE; 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); std::string s(buf.data(), dwLen);
char szNew[128]; char szNew[128];
// 更新 filevers 元组 // 更新 filevers 元组
{ {
const std::string key = "filevers=("; const std::string key = "filevers=(";
auto p = s.find(key); auto p = s.find(key);
@@ -394,7 +394,7 @@ static BOOL UpdateVersionInfoFile(LPCTSTR lpPath, LPCTSTR lpProductVersion, LPCT
} }
} }
} }
// 更新 prodvers 元组 // 更新 prodvers 元组
{ {
const std::string key = "prodvers=("; const std::string key = "prodvers=(";
auto p = s.find(key); 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'"; const std::string key = "u'FileVersion', u'";
auto p = s.find(key); auto p = s.find(key);
@@ -418,7 +418,7 @@ static BOOL UpdateVersionInfoFile(LPCTSTR lpPath, LPCTSTR lpProductVersion, LPCT
s.replace(pValStart, pValEnd - pValStart, szFileVer); s.replace(pValStart, pValEnd - pValStart, szFileVer);
} }
} }
// 更新 ProductVersion 字符串值 // 更新 ProductVersion 字符串值
{ {
const std::string key = "u'ProductVersion', u'"; const std::string key = "u'ProductVersion', u'";
auto p = s.find(key); auto p = s.find(key);
@@ -438,13 +438,16 @@ static BOOL UpdateVersionInfoFile(LPCTSTR lpPath, LPCTSTR lpProductVersion, LPCT
int HandlePyinstallerBuildCommand(int argc, TCHAR* argv[]) int HandlePyinstallerBuildCommand(int argc, TCHAR* argv[])
{ {
// 解析 pid内嵌在 argv[1] 中,如 "pyinstaller=5" #ifdef _DEBUG
// "pyinstaller=" 为 13 个字符 Sleep(15000);
CString strPid = CString(argv[1]).Mid(13); #endif
// 解析 pid内嵌在 argv[1] 中,如 "pyinstaller=5"
// "pyinstaller=" 为 12 个字符
UINT nPid = 0; UINT nPid = 0;
CString strPid = CString(argv[1]).Mid(12);
if (strPid.IsEmpty() || !TryParseUInt16(strPid, nPid)) 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; return 41;
} }
@@ -468,6 +471,7 @@ int HandlePyinstallerBuildCommand(int argc, TCHAR* argv[])
strFileVersion, strFileVersion,
DEFAULT_MAJOR_WHEN_NO_TAG_FOR_PYINSTALLER, DEFAULT_MAJOR_WHEN_NO_TAG_FOR_PYINSTALLER,
DEFAULT_MINOR_WHEN_NO_TAG_FOR_PYINSTALLER); DEFAULT_MINOR_WHEN_NO_TAG_FOR_PYINSTALLER);
if (nVersionRet != 0) if (nVersionRet != 0)
{ {
return nVersionRet; return nVersionRet;
@@ -481,32 +485,31 @@ int HandlePyinstallerBuildCommand(int argc, TCHAR* argv[])
{ {
strProductVersion = strProductVersion.Left(nDot2 + 1) + _T("0.0"); 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); CString strFirstToken = ExtractFirstToken(strExtraArgs);
BOOL bIsSpec = (strFirstToken.GetLength() >= 5 BOOL bIsSpec = (strFirstToken.GetLength() >= 5 && strFirstToken.Right(5).CompareNoCase(_T(".spec")) == 0);
&& strFirstToken.Right(5).CompareNoCase(_T(".spec")) == 0);
if (bIsSpec) if (bIsSpec)
{ {
// 解析 spec 文件完整路径 // 解析 spec 文件完整路径
CString strSpecPath; CString strSpecPath;
if (PathIsRelative(strFirstToken)) if (PathIsRelative(strFirstToken))
strSpecPath.Format(_T("%s\\%s"), lpRepoPath, strFirstToken.GetString()); strSpecPath.Format(_T("%s\\%s"), lpRepoPath, strFirstToken.GetString());
else else
strSpecPath = strFirstToken; strSpecPath = strFirstToken;
// 获取 spec 所在目录 // 获取 spec 所在目录
TCHAR szSpecDir[MAX_PATH] = {}; TCHAR szSpecDir[MAX_PATH] = {};
_tcscpy_s(szSpecDir, strSpecPath.GetString()); _tcscpy_s(szSpecDir, strSpecPath.GetString());
PathRemoveFileSpec(szSpecDir); PathRemoveFileSpec(szSpecDir);
// 检查 spec 是否已指定版本信息文件 // 检查 spec 是否已指定版本信息文件
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());
@@ -515,45 +518,43 @@ int HandlePyinstallerBuildCommand(int argc, TCHAR* argv[])
if (!UpdateVersionInfoFile(strVersionInfoFullPath, strProductVersion, strFileVersion)) if (!UpdateVersionInfoFile(strVersionInfoFullPath, strProductVersion, strFileVersion))
{ {
_tprintf(_T("错误: 无法更新版本信息文件: %s。\n"), strVersionInfoFullPath.GetString()); _tprintf(_T("错误: 无法更新版本信息文件: %s。\n"), strVersionInfoFullPath.GetString());
return 45; return 45;
} }
_tprintf(_T("版本信息文件已更新: %s\n"), strVersionInfoFullPath.GetString()); _tprintf(_T("版本信息文件已更新: %s\n"), strVersionInfoFullPath.GetString());
} }
else else
{ {
// spec 未指定 version=,生成 version_info.txt 并注入 spec // spec 未指定 version=,生成 version_info.txt 并注入 spec
CString strVersionInfoPath; CString strVersionInfoPath;
if (!WriteVersionInfoFile(szSpecDir, strProductVersion, strFileVersion, 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; return 45;
} }
_tprintf(_T("版本信息文件已生成: %s\n"), strVersionInfoPath.GetString()); _tprintf(_T("版本信息文件已生成: %s\n"), strVersionInfoPath.GetString());
if (!InjectVersionIntoSpec(strSpecPath)) if (!InjectVersionIntoSpec(strSpecPath))
{ {
_tprintf(_T("错误: 无法修改 spec 文件: %s。\n"), strSpecPath.GetString()); _tprintf(_T("错误: 无法修改 spec 文件: %s。\n"), strSpecPath.GetString());
return 46; return 46;
} }
} }
} }
_tprintf(_T("PyInstaller 打包开始..\n")); _tprintf(_T("PyInstaller 打包开始..\n"));
_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());
CString strCmd; CString strCmd;
strCmd.Format( strCmd.Format(_T("cmd /c python -m PyInstaller %s"),strExtraArgs.GetString());
_T("cmd /c python -m PyInstaller %s"),
strExtraArgs.GetString());
CString strBuildResult = StartProcess(NULL, strCmd.GetBuffer(), lpRepoPath); CString strBuildResult = StartProcess(NULL, strCmd.GetBuffer(), lpRepoPath);
strCmd.ReleaseBuffer(); strCmd.ReleaseBuffer();
if (strBuildResult.IsEmpty()) if (strBuildResult.IsEmpty())
{ {
_tprintf(_T("错误: PyInstaller 打包失败。\n")); _tprintf(_T("错误: PyInstaller 打包失败。\n"));
} }
return 0; return 0;

View File

@@ -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=<pid>` 获取版本,再用脚本替换 `version_info.txt` 中的版本字段,然后执行 `gitver pyinstaller=<pid> params="myapp.spec"`。
--- ---
## 错误码 ## 错误码