|
@@ -64,6 +64,9 @@ PRODUCTS_DIR = 'outputs' # 产物仓: 默认打进包 (页面开箱有
|
|
|
# `docs\振动数据接入_v0.1.md` 与 `docs\数据目录结构与落位约定_v0.2.md`, 目录本身按用户令删除)。
|
|
# `docs\振动数据接入_v0.1.md` 与 `docs\数据目录结构与落位约定_v0.2.md`, 目录本身按用户令删除)。
|
|
|
VERSION = '0.4.0' # 包版本 (2026-09-16): 写进 dist-manifest.json 与默认文件名
|
|
VERSION = '0.4.0' # 包版本 (2026-09-16): 写进 dist-manifest.json 与默认文件名
|
|
|
INCLUDE_FILES = ['guanlan.py', 'install.bat', 'install.ps1', 'install.sh', 'check.bat', 'start.bat', 'stop.bat',
|
|
INCLUDE_FILES = ['guanlan.py', 'install.bat', 'install.ps1', 'install.sh', 'check.bat', 'start.bat', 'stop.bat',
|
|
|
|
|
+ 'start_hidden.vbs', # ★ 无窗口启动: start.bat 会调它。v0.4.0 第一版漏了它 → 目标机 start.bat 报
|
|
|
|
|
+ # "无法找到脚本文件 start_hidden.vbs" (2026-09-16 现场反馈)。
|
|
|
|
|
+ # 现在除了补进本清单, 还由 src/entry_refs.py **从引用反推**兜底(漏不掉)。
|
|
|
'requirements.txt', 'README_先读我.txt', '测试须知.txt']
|
|
'requirements.txt', 'README_先读我.txt', '测试须知.txt']
|
|
|
EXCLUDE_DIRS = {'.git', '.venv', '.github', 'logs', 'run', '__pycache__'}
|
|
EXCLUDE_DIRS = {'.git', '.venv', '.github', 'logs', 'run', '__pycache__'}
|
|
|
# ★ node_modules **不排除**: `release/viewer/node_modules` 里是 esbuild(构建工具)+three(三维库源码), 共 52.5 MB;
|
|
# ★ node_modules **不排除**: `release/viewer/node_modules` 里是 esbuild(构建工具)+three(三维库源码), 共 52.5 MB;
|
|
@@ -107,6 +110,18 @@ def plan(with_data=False, no_products=False):
|
|
|
p = ROOT / f
|
|
p = ROOT / f
|
|
|
if p.is_file():
|
|
if p.is_file():
|
|
|
inc.append(p)
|
|
inc.append(p)
|
|
|
|
|
+ else:
|
|
|
|
|
+ print(f' [!] 清单里的 {f} 不在源码树里 —— 交付包会少一个入口文件, 请查')
|
|
|
|
|
+ # ★ 引用闭合兜底 (2026-09-16 现场实炸后加): 凡是入口脚本 (.bat/.vbs/.ps1/.sh) 引用到的、本机真实存在的
|
|
|
|
|
+ # 根目录文件, 一律自动补进包。手工清单 `INCLUDE_FILES` 漏一个文件, 代价是目标机双击 start.bat 直接报错;
|
|
|
|
|
+ # 这条兜底让"漏"变得不可能 —— 上面那个 [X] 才是靠人盯的。
|
|
|
|
|
+ from src.entry_refs import auto_include, referenced_tree_files
|
|
|
|
|
+ got = {p.relative_to(ROOT).as_posix().lower() for p in inc}
|
|
|
|
|
+ extra = auto_include(ROOT, got)
|
|
|
|
|
+ if extra:
|
|
|
|
|
+ for p in extra:
|
|
|
|
|
+ print(f' [+] 自动补入 (被入口脚本引用): {p.name}')
|
|
|
|
|
+ inc.extend(extra)
|
|
|
skipped = [('.git/', '版本库'), ('.venv/', '虚拟环境(换机必失效, 目标机安装时重建)')]
|
|
skipped = [('.git/', '版本库'), ('.venv/', '虚拟环境(换机必失效, 目标机安装时重建)')]
|
|
|
_glob_why = {
|
|
_glob_why = {
|
|
|
'_products_off*': '历史遗留的"清除产物"暂存档 (2026-09-16 起清除=真删, 不再产生; 老机器上若有可手工删)',
|
|
'_products_off*': '历史遗留的"清除产物"暂存档 (2026-09-16 起清除=真删, 不再产生; 老机器上若有可手工删)',
|
|
@@ -120,12 +135,25 @@ def plan(with_data=False, no_products=False):
|
|
|
return inc, skipped
|
|
return inc, skipped
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+def keep(f: pathlib.Path) -> bool:
|
|
|
|
|
+ """这个文件真会被写进包吗 —— 打包循环与"预演件数"共用同一条判据。
|
|
|
|
|
+
|
|
|
|
|
+ ★ 2026-09-16 修正: 原实现里 `size_of()` 数的是**目录下所有文件**(连 `__pycache__/*.pyc` 一起数),
|
|
|
|
|
+ 而实际写入时又跳过它们 ⇒ 预演报的"件数/体积"比包里真的多 (src 少报 100+ 件)。
|
|
|
|
|
+ 现在两处都走 `keep()`: 预演数字 = 包里条目数, 对不上就是 bug。
|
|
|
|
|
+ """
|
|
|
|
|
+ parts = f.relative_to(ROOT).parts
|
|
|
|
|
+ if any(x in EXCLUDE_DIRS for x in parts) or f.suffix == '.pyc':
|
|
|
|
|
+ return False
|
|
|
|
|
+ return not any(x.startswith(tuple(g.rstrip('*') for g in EXCLUDE_GLOBS)) for x in parts[:1])
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
def size_of(p: pathlib.Path):
|
|
def size_of(p: pathlib.Path):
|
|
|
if p.is_file():
|
|
if p.is_file():
|
|
|
return 1, p.stat().st_size
|
|
return 1, p.stat().st_size
|
|
|
n = s = 0
|
|
n = s = 0
|
|
|
for f in p.rglob('*'):
|
|
for f in p.rglob('*'):
|
|
|
- if f.is_file():
|
|
|
|
|
|
|
+ if f.is_file() and keep(f):
|
|
|
n += 1; s += f.stat().st_size
|
|
n += 1; s += f.stat().st_size
|
|
|
return n, s
|
|
return n, s
|
|
|
|
|
|
|
@@ -146,7 +174,8 @@ def build(out: pathlib.Path, with_data=False, progress=True, no_products=False)
|
|
|
out.parent.mkdir(parents=True, exist_ok=True)
|
|
out.parent.mkdir(parents=True, exist_ok=True)
|
|
|
man = dict(version=VERSION, built=time.strftime('%Y-%m-%d %H:%M:%S'), root=str(ROOT),
|
|
man = dict(version=VERSION, built=time.strftime('%Y-%m-%d %H:%M:%S'), root=str(ROOT),
|
|
|
includes=[p.relative_to(ROOT).as_posix() for p in inc],
|
|
includes=[p.relative_to(ROOT).as_posix() for p in inc],
|
|
|
- excluded={n: w for n, w in skipped}, files=tot_n, bytes=tot_s,
|
|
|
|
|
|
|
+ excluded={n: w for n, w in skipped}, files=tot_n + 1, # +1 = dist-manifest.json 自身
|
|
|
|
|
+ bytes=tot_s,
|
|
|
no_data=not with_data, no_products=bool(no_products), no_logs=True,
|
|
no_data=not with_data, no_products=bool(no_products), no_logs=True,
|
|
|
note='本包由 scripts/pack_dist.py 生成; **不含 输入数据(data/) · 产物(outputs/) · 日志(logs/, run/)** '
|
|
note='本包由 scripts/pack_dist.py 生成; **不含 输入数据(data/) · 产物(outputs/) · 日志(logs/, run/)** '
|
|
|
'(2026-09-16 用户令)。目标机解压后跑 install.bat(Windows) 或 sh install.sh(Linux/macOS), '
|
|
'(2026-09-16 用户令)。目标机解压后跑 install.bat(Windows) 或 sh install.sh(Linux/macOS), '
|
|
@@ -163,18 +192,26 @@ def build(out: pathlib.Path, with_data=False, progress=True, no_products=False)
|
|
|
done += 1
|
|
done += 1
|
|
|
else:
|
|
else:
|
|
|
for f in p.rglob('*'):
|
|
for f in p.rglob('*'):
|
|
|
- if not f.is_file():
|
|
|
|
|
- continue
|
|
|
|
|
- parts = f.relative_to(ROOT).parts
|
|
|
|
|
- if any(x in EXCLUDE_DIRS for x in parts) or f.suffix == '.pyc':
|
|
|
|
|
- continue
|
|
|
|
|
- if any(x.startswith(tuple(g.rstrip('*') for g in EXCLUDE_GLOBS)) for x in parts[:1]):
|
|
|
|
|
|
|
+ if not f.is_file() or not keep(f):
|
|
|
continue
|
|
continue
|
|
|
z.write(f, f.relative_to(ROOT).as_posix())
|
|
z.write(f, f.relative_to(ROOT).as_posix())
|
|
|
done += 1
|
|
done += 1
|
|
|
if progress and done % 500 < 1:
|
|
if progress and done % 500 < 1:
|
|
|
print(f' … 已写入 {done} 件', flush=True)
|
|
print(f' … 已写入 {done} 件', flush=True)
|
|
|
print(f'\n完成: {out} {out.stat().st_size/1e6:.0f} MB (源 {tot_s/1e6:.0f} MB), 条目 {done} 件, 耗时 {time.time()-t0:.0f}s')
|
|
print(f'\n完成: {out} {out.stat().st_size/1e6:.0f} MB (源 {tot_s/1e6:.0f} MB), 条目 {done} 件, 耗时 {time.time()-t0:.0f}s')
|
|
|
|
|
+
|
|
|
|
|
+ # 入口引用闭合核对 (对着**包内条目**查; 2026-09-16: v0.4.0 第一版漏带 start_hidden.vbs, 目标机 start.bat 报错)
|
|
|
|
|
+ from src.entry_refs import missing_refs
|
|
|
|
|
+ with zipfile.ZipFile(out) as z:
|
|
|
|
|
+ names = z.namelist()
|
|
|
|
|
+ miss = missing_refs(ROOT, names)
|
|
|
|
|
+ if miss:
|
|
|
|
|
+ for e, rel in miss:
|
|
|
|
|
+ print(f' [X] 包内 {e} 引用了 {rel}, 但包里没有 —— 这个包不能交付')
|
|
|
|
|
+ out.unlink()
|
|
|
|
|
+ print(f' [X] 已删除不合格的包 {out.name} (留着会被人当成能用的包发出去)')
|
|
|
|
|
+ return 1
|
|
|
|
|
+ print(f' [OK] 入口引用闭合: 包内每个入口脚本引用的文件都在包里 (条目 {len(names)} 件)')
|
|
|
print('目标机用法: 解压到无空格无中文路径 → install.bat / sh install.sh → check → start(自动开门户)')
|
|
print('目标机用法: 解压到无空格无中文路径 → install.bat / sh install.sh → check → start(自动开门户)')
|
|
|
print(f'开箱验证(推荐): python scripts/pack_dist.py --verify "{out}"')
|
|
print(f'开箱验证(推荐): python scripts/pack_dist.py --verify "{out}"')
|
|
|
return 0
|
|
return 0
|
|
@@ -191,6 +228,14 @@ def verify(zip_path: pathlib.Path, keep=False) -> int:
|
|
|
z.extractall(tmp)
|
|
z.extractall(tmp)
|
|
|
n = sum(1 for _ in tmp.rglob('*') if _.is_file())
|
|
n = sum(1 for _ in tmp.rglob('*') if _.is_file())
|
|
|
print(f' {n} 件, 耗时 {time.time()-t0:.0f}s')
|
|
print(f' {n} 件, 耗时 {time.time()-t0:.0f}s')
|
|
|
|
|
+ print('①b 入口引用闭合核对 (包内 start.bat → start_hidden.vbs 之类)')
|
|
|
|
|
+ from src.entry_refs import missing_refs
|
|
|
|
|
+ miss = missing_refs(tmp)
|
|
|
|
|
+ if miss:
|
|
|
|
|
+ for e, rel in miss:
|
|
|
|
|
+ print(f' [X] {e} 引用了 {rel}, 但解压目录里没有 —— 目标机会双击即报错')
|
|
|
|
|
+ return 1
|
|
|
|
|
+ print(' [OK] 全部在位')
|
|
|
print('② 离线安装')
|
|
print('② 离线安装')
|
|
|
xt = _xfer()
|
|
xt = _xfer()
|
|
|
py = xt.install_in(tmp)
|
|
py = xt.install_in(tmp)
|