-
Notifications
You must be signed in to change notification settings - Fork 23
Expand file tree
/
Copy pathbuild_nuitka.py
More file actions
289 lines (240 loc) · 8.17 KB
/
build_nuitka.py
File metadata and controls
289 lines (240 loc) · 8.17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
"""
Nuitka 打包脚本
用于构建 SecRandom 的独立可执行文件
"""
import subprocess
import sys
import re
from pathlib import Path
# 设置Windows控制台编码为UTF-8
if sys.platform == "win32":
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8")
from packaging_utils import (
ADDITIONAL_HIDDEN_IMPORTS,
ICON_FILE,
PROJECT_ROOT,
collect_data_includes,
collect_language_modules,
collect_view_modules,
normalize_hidden_imports,
)
# 导入项目配置信息
sys.path.insert(0, str(Path(__file__).parent))
from app.tools.variable import APPLY_NAME, VERSION, APP_DESCRIPTION, AUTHOR, WEBSITE
# 导入deb包构建工具
from packaging_utils_deb import DebBuilder
PACKAGE_INCLUDE_NAMES = {
"app.Language.modules",
"app.view",
"app.tools",
"app.page_building",
}
def _print_packaging_summary() -> None:
"""Log a quick overview of the data and modules that will be bundled."""
data_includes = collect_data_includes()
hidden_names = normalize_hidden_imports(
collect_language_modules() + collect_view_modules() + ADDITIONAL_HIDDEN_IMPORTS
)
package_names = sorted(
{name for name in hidden_names if "." not in name} | PACKAGE_INCLUDE_NAMES
)
module_names = [name for name in hidden_names if "." in name]
print("\nSelected data includes ({} entries):".format(len(data_includes)))
for item in data_includes:
kind = "dir " if item.is_dir else "file"
print(f" - {kind} {item.source} -> {item.target}")
print("\nRequired packages ({} entries):".format(len(package_names)))
for pkg in package_names:
print(f" - {pkg}")
print("\nHidden modules ({} entries):".format(len(module_names)))
for mod in module_names:
print(f" - {mod}")
def _gather_data_flags() -> list[str]:
"""收集数据文件包含标志"""
flags: list[str] = []
for include in collect_data_includes():
flag = "--include-data-dir" if include.is_dir else "--include-data-file"
source = include.source
target = include.target
# FIX: Nuitka 不允许 file 目标为 "."
if not include.is_dir and target == ".":
target = Path(source).name
flags.append(f"{flag}={source}={target}")
return flags
def _gather_module_and_package_flags() -> tuple[list[str], list[str]]:
"""收集模块和包包含标志"""
hidden_names = normalize_hidden_imports(
collect_language_modules() + collect_view_modules() + ADDITIONAL_HIDDEN_IMPORTS
)
package_names = set(PACKAGE_INCLUDE_NAMES)
module_names: list[str] = []
for name in hidden_names:
if "." not in name:
package_names.add(name)
else:
module_names.append(name)
package_flags = [f"--include-package={pkg}" for pkg in sorted(package_names)]
module_flags = [f"--include-module={mod}" for mod in module_names]
return module_flags, package_flags
def _sanitize_version(ver_str: str) -> str:
"""清理版本字符串,确保符合Nuitka要求"""
if not ver_str:
return "0.0.0.0"
ver_str = ver_str.lstrip("vV").strip()
match = re.match(r"^(\d+(\.\d+)*)", ver_str)
if match:
clean_ver = match.group(1)
if "." not in clean_ver:
clean_ver += ".0"
return clean_ver
return "0.0.0.0"
def get_nuitka_command() -> list[str]:
"""获取Nuitka命令列表"""
raw_version = VERSION if VERSION else "0.0.0"
clean_version = _sanitize_version(raw_version)
print(f"\n版本号处理: '{raw_version}' -> '{clean_version}'")
module_flags, package_flags = _gather_module_and_package_flags()
cmd = [
"uv",
"run",
"-m",
"nuitka",
"--standalone",
"--onefile",
"--enable-plugin=pyside6",
"--assume-yes-for-downloads",
"--output-dir=dist",
"--product-name=SecRandom",
"--file-description=公平随机抽取系统",
f"--product-version={clean_version}",
f"--file-version={clean_version}",
"--copyright=Copyright (c) 2025",
"--no-deployment-flag=self-execution",
]
# 编译器选择逻辑
if sys.platform == "win32":
# 检测是否为 Python 3.13 及以上
if sys.version_info >= (3, 13):
print("\n[注意] 检测到 Python 3.13+")
print(" Nuitka 暂不支持在此版本使用 MinGW64。")
print(
" 将自动切换为 MSVC (Visual Studio)。请确保已安装 C++ 生成工具。"
)
cmd.append("--msvc=latest")
else:
# Python 3.12 及以下使用 MinGW64
cmd.append("--mingw64")
else:
cmd.append("--linux-onefile-icon")
cmd.extend(_gather_data_flags())
cmd.extend(package_flags)
cmd.extend(module_flags)
if sys.platform == "win32" and ICON_FILE.exists():
cmd.append(f"--windows-icon-from-ico={ICON_FILE}")
elif sys.platform == "linux" and ICON_FILE.exists():
cmd.append(f"--linux-icon={ICON_FILE}")
cmd.append("main.py")
return cmd
def check_compiler_env() -> bool:
"""检查编译器环境"""
if sys.platform != "win32":
return True
# 如果是 Python 3.13+,需要检查 MSVC(这里简单略过,交给 Nuitka 报错,因为检测 MSVC 比较复杂)
if sys.version_info >= (3, 13):
return True
# 如果是 Python < 3.13,检查 MinGW64
print("\n检查 MinGW64 环境...")
try:
result = subprocess.run(
["gcc", "--version"],
capture_output=True,
text=True,
check=False,
encoding="utf-8",
errors="replace",
)
if result.returncode == 0:
print(
f"✓ 找到 GCC: {result.stdout.splitlines()[0] if result.stdout else 'Unknown'}"
)
return True
except FileNotFoundError:
pass
# 简单检查路径
common_paths = [
r"C:\msys64\mingw64\bin",
r"C:\mingw64\bin",
r"C:\Program Files\mingw64\bin",
]
for path in common_paths:
if (Path(path) / "gcc.exe").exists():
print(f"✓ 找到 MinGW64: {path}")
return True
print("⚠ 警告: 未找到 MinGW64,Nuitka 可能会尝试自动下载。")
return input("是否继续? (y/n): ").lower() == "y"
def build_deb() -> None:
"""构建deb包"""
if sys.platform != "linux":
return
print("\n" + "=" * 60)
print("开始构建deb包...")
print("=" * 60)
try:
DebBuilder.build_from_nuitka(
PROJECT_ROOT, APPLY_NAME, VERSION, APP_DESCRIPTION, AUTHOR, WEBSITE
)
print("=" * 60)
except Exception as e:
print(f"构建deb包失败: {e}")
sys.exit(1)
def main():
"""执行 Nuitka 打包"""
print("=" * 60)
print("开始使用 Nuitka + uv 打包 SecRandom")
print("=" * 60)
if sys.platform == "win32" and not check_compiler_env():
sys.exit(1)
_print_packaging_summary()
cmd = get_nuitka_command()
# 打印命令
print("\n执行命令:")
print(" ".join(cmd))
print("\n" + "=" * 60)
# 执行打包
try:
subprocess.run(
cmd,
check=True,
cwd=PROJECT_ROOT,
capture_output=False,
text=True,
encoding="utf-8",
errors="replace",
)
print("\n" + "=" * 60)
print("Nuitka打包成功!")
print("=" * 60)
# 构建deb包(仅在Linux平台)
build_deb()
except subprocess.CalledProcessError as e:
print("\n" + "=" * 60)
print(f"打包失败: {e}")
print(f"返回码: {e.returncode}")
print("=" * 60)
sys.exit(1)
except KeyboardInterrupt:
print("\n" + "=" * 60)
print("用户取消打包")
print("=" * 60)
sys.exit(1)
except Exception as e:
print("\n" + "=" * 60)
print(f"发生意外错误: {e}")
import traceback
traceback.print_exc()
print("=" * 60)
sys.exit(1)
if __name__ == "__main__":
main()