修复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)
{
_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("错误: 未知的参数 %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;

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"`。
---
## 错误码