124 lines
4.8 KiB
Python
124 lines
4.8 KiB
Python
"""
|
||
Nuitka 打包资源档位 / 链接器注入 测试(不触发真实编译)
|
||
|
||
覆盖 build.py 里 nuitka 打包的三处防坑逻辑:
|
||
1. 链接器:装了 ld.lld 时自动经 LDFLAGS 注入 -fuse-ld=lld(默认 GNU ld 链接大工程
|
||
峰值数 GB,小内存机器/WSL 会被 OOM 杀掉:"clang: unable to execute command: Killed");
|
||
已有 LDFLAGS 时不覆盖(用户在 CHONGMING_NUITKA_PGO 之外自行指定链接器优先)
|
||
2. 内存档位:可用内存 ≥ NUITKA_LTO_MIN_GB 完整优化(LTO);低于则去 LTO(LTO 链接
|
||
峰值 3 GB+,实测 ld/lld 都被 OOM 杀过);更低(或 CHONGMING_NUITKA_LOW_MEM=1)
|
||
进一步 --low-memory + 降并发;CHONGMING_NUITKA_FULL=1 强制完整优化
|
||
3. PGO 缺省关闭:--pgo-c 与 standalone/onefile 不兼容且 profiling 运行会等程序退出
|
||
(常驻服务卡死),只能由 CHONGMING_NUITKA_PGO=1 显式打开——
|
||
这里用源码断言兜住"默认关"这条(防止回潮)
|
||
|
||
运行: uv run python test/test_nuitka_pack_flags.py
|
||
"""
|
||
import io
|
||
import contextlib
|
||
import os
|
||
import shutil
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
ROOT = Path(__file__).parent.parent
|
||
sys.path.insert(0, str(ROOT / "utils" / "cli" / "src"))
|
||
|
||
from chongming_cli import build as b # noqa: E402
|
||
|
||
failures = []
|
||
|
||
|
||
def check(name, cond, detail=""):
|
||
status = "PASS" if cond else "FAIL"
|
||
print(f"[{status}] {name}" + (f" — {detail}" if detail else ""))
|
||
if not cond:
|
||
failures.append(name)
|
||
|
||
|
||
def quiet_note():
|
||
"""调 _nuitka_env_and_mem_note 时屏蔽它打印的降档提示。"""
|
||
with contextlib.redirect_stdout(io.StringIO()):
|
||
return b._nuitka_env_and_mem_note()
|
||
|
||
|
||
saved = {k: os.environ.pop(k, None) for k in
|
||
(b.NUITKA_LOW_MEM_ENV, b.NUITKA_FULL_ENV, b.NUITKA_PGO_ENV)}
|
||
|
||
try:
|
||
# ========== 1. 链接器注入 ==========
|
||
saved_ldflags = os.environ.pop("LDFLAGS", None)
|
||
env, note, profile = quiet_note()
|
||
if sys.platform.startswith("linux") and shutil.which("ld.lld"):
|
||
check("装了 lld 自动注入 -fuse-ld=lld", env.get("LDFLAGS", "").endswith("-fuse-ld=lld"), env.get("LDFLAGS"))
|
||
check("成功信息回显链接器", "lld" in note, note)
|
||
else:
|
||
check("无 lld 时不注入(本机无 lld,跳过)", "-fuse-ld" not in (env.get("LDFLAGS") or ""))
|
||
|
||
os.environ["LDFLAGS"] = "-Wl,--as-needed"
|
||
env, _, _ = quiet_note()
|
||
check("已有 LDFLAGS 时追加而不覆盖", env["LDFLAGS"].startswith("-Wl,--as-needed"), env["LDFLAGS"])
|
||
if saved_ldflags is None:
|
||
os.environ.pop("LDFLAGS", None)
|
||
else:
|
||
os.environ["LDFLAGS"] = saved_ldflags
|
||
|
||
# ========== 2. 内存档位 ==========
|
||
mem = b._mem_available_gb()
|
||
check("_mem_available_gb 可读(Linux)", mem > 0 or not sys.platform.startswith("linux"), f"{mem:.1f} GB")
|
||
|
||
env, note, profile = quiet_note()
|
||
if mem >= 0:
|
||
expect = (
|
||
"squeeze" if mem < b.NUITKA_SQUEEZE_GB
|
||
else "no-lto" if mem < b.NUITKA_LTO_MIN_GB
|
||
else "full"
|
||
)
|
||
check(
|
||
"默认档位与内存阈值一致",
|
||
profile == expect,
|
||
f"{mem:.1f} GB → {profile}(阈值 {b.NUITKA_SQUEEZE_GB}/{b.NUITKA_LTO_MIN_GB} GB)",
|
||
)
|
||
check(
|
||
"非 full 档在提示里说明原因",
|
||
(profile == "full" and "LTO" not in note) or (
|
||
profile == "no-lto" and "未启用 LTO" in note
|
||
) or (profile == "squeeze" and "--low-memory" in note),
|
||
note,
|
||
)
|
||
|
||
os.environ[b.NUITKA_LOW_MEM_ENV] = "1"
|
||
env, note, profile = quiet_note()
|
||
check(f"{b.NUITKA_LOW_MEM_ENV}=1 强制 squeeze 档", profile == "squeeze" and "--low-memory" in note, note)
|
||
|
||
os.environ.pop(b.NUITKA_LOW_MEM_ENV)
|
||
os.environ[b.NUITKA_FULL_ENV] = "1"
|
||
env, note, profile = quiet_note()
|
||
check(f"{b.NUITKA_FULL_ENV}=1 强制完整优化(覆盖内存自动判断)", profile == "full" and "--low-memory" not in note, note)
|
||
os.environ.pop(b.NUITKA_FULL_ENV)
|
||
|
||
# ========== 3. PGO 缺省关闭(源码兜底,防回潮) ==========
|
||
src = (ROOT / "utils" / "cli" / "src" / "chongming_cli" / "build.py").read_text(encoding="utf-8")
|
||
check(
|
||
"PGO 参数只在 NUITKA_PGO_ENV 开关下出现",
|
||
f'os.environ.get(NUITKA_PGO_ENV) == "1"' in src and b.NUITKA_PGO_ENV == "CHONGMING_NUITKA_PGO",
|
||
)
|
||
check(
|
||
"Nuitka 缺 patchelf 时提前失败(链接后才检查,白跑一整轮)",
|
||
"patchelf" in src and "缺少 patchelf" in src,
|
||
)
|
||
check(
|
||
"Nuitka 失败会抛错而不是静默返回(构建要非零退出)",
|
||
'raise RuntimeError("Nuitka 编译失败' in src,
|
||
)
|
||
finally:
|
||
for k, v in saved.items():
|
||
if v is not None:
|
||
os.environ[k] = v
|
||
|
||
print("\n" + ("=" * 40))
|
||
if failures:
|
||
print(f"❌ {len(failures)} 项未通过: {failures}")
|
||
sys.exit(1)
|
||
print("✅ 全部通过")
|