|
@@ -113,8 +113,20 @@ EXCLUDE_GLOBS = ['_products_off*', 'data', 'outputs']
|
|
|
# .log/.jsonl 运行日志与审计流水 —— **不管躺在哪都不进包**; .tmp/.bak/.old/.orig/.rej/.swp 编辑器临时与备份
|
|
# .log/.jsonl 运行日志与审计流水 —— **不管躺在哪都不进包**; .tmp/.bak/.old/.orig/.rej/.swp 编辑器临时与备份
|
|
|
EXCLUDE_SUFFIX = {'.pyc', '.pyo', '.lnk', '.log', '.jsonl',
|
|
EXCLUDE_SUFFIX = {'.pyc', '.pyo', '.lnk', '.log', '.jsonl',
|
|
|
'.tmp', '.temp', '.bak', '.old', '.orig', '.rej', '.swp', '.swo'}
|
|
'.tmp', '.temp', '.bak', '.old', '.orig', '.rej', '.swp', '.swo'}
|
|
|
-# 文件名/尾部模式 (glob): 临时与备份的常见写法 + 系统索引文件
|
|
|
|
|
-EXCLUDE_NAMES = ('*~', '~$*', '*.bak-*', '.DS_Store', 'Thumbs.db', 'desktop.ini', '*.crdownload', '*.partial')
|
|
|
|
|
|
|
+# 文件名/尾部模式 (glob): 临时与备份的常见写法 + 系统索引文件 + **本机安装记录**
|
|
|
|
|
+# ★ `install-info.json` 必须排除 (2026-09-17 实逮: 它被原样打进了交付包): 那是**本机装没装、装的哪一版**
|
|
|
|
|
+# 的凭据, 里面写着安装时间与本机绝对路径; 随包发出去, 目标机第一次装就会读到它, 于是安装前检查
|
|
|
|
|
+# 报"已安装 v2.5.0(装于 <打包机时间>)", 明明是新机器却提示"版本相同, 是否重装" —— 用户令 1
|
|
|
|
|
+# 要的版本检查当场失灵。它是装机产物, 不是交付件。
|
|
|
|
|
+EXCLUDE_NAMES = ('install-info.json', '*~', '~$*', '*.bak-*', '.DS_Store', 'Thumbs.db', 'desktop.ini',
|
|
|
|
|
+ '*.crdownload', '*.partial')
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def is_machine_artifact(p: pathlib.Path) -> bool:
|
|
|
|
|
+ """装机产物(本机装没装 / 本机快捷方式)—— 只在本机有效, 不随包、不入库 (2026-09-17)。"""
|
|
|
|
|
+ import fnmatch
|
|
|
|
|
+ return (p.name == 'install-info.json' or p.suffix.lower() in EXCLUDE_SUFFIX
|
|
|
|
|
+ or any(fnmatch.fnmatch(p.name, pat) for pat in EXCLUDE_NAMES))
|
|
|
|
|
|
|
|
|
|
|
|
|
def _xfer():
|
|
def _xfer():
|
|
@@ -159,10 +171,19 @@ def plan(with_data=False, with_products=False):
|
|
|
from src.entry_refs import auto_include, referenced_tree_files
|
|
from src.entry_refs import auto_include, referenced_tree_files
|
|
|
got = {p.relative_to(ROOT).as_posix().lower() for p in inc}
|
|
got = {p.relative_to(ROOT).as_posix().lower() for p in inc}
|
|
|
extra = auto_include(ROOT, got)
|
|
extra = auto_include(ROOT, got)
|
|
|
- if extra:
|
|
|
|
|
- for p in extra:
|
|
|
|
|
|
|
+ # ★ 装机产物不许被"引用闭合"兜底补进包 (2026-09-17 实逮): `install.ps1`/`install.sh` 的版本检查那步
|
|
|
|
|
+ # 会读 `install-info.json`, 于是自动补入把它当"入口引用的文件"补了进来 —— 而它是**本机装没装**的
|
|
|
|
|
+ # 凭据(含安装时间与本机路径), 目标机第一次装就会误报"已安装、版本相同"。引用闭合管的是"入口点名的
|
|
|
|
|
+ # 文件必须在位", 不管"这个文件该不该随包", 所以这里按排除规则再滤一遍并如实说一句。
|
|
|
|
|
+ keepable, dropped = [], []
|
|
|
|
|
+ for p in extra:
|
|
|
|
|
+ (dropped if is_machine_artifact(p) else keepable).append(p)
|
|
|
|
|
+ if keepable:
|
|
|
|
|
+ for p in keepable:
|
|
|
print(f' [+] 自动补入 (被入口脚本引用): {p.name}')
|
|
print(f' [+] 自动补入 (被入口脚本引用): {p.name}')
|
|
|
- inc.extend(extra)
|
|
|
|
|
|
|
+ inc.extend(keepable)
|
|
|
|
|
+ for p in dropped:
|
|
|
|
|
+ print(f' [i] 不进包 (装机产物, 目标机安装时自建): {p.name}')
|
|
|
skipped = [('data/', '① 输入数据 (现场原始件): 用户令不进包; 要一起交付用 --with-data')]
|
|
skipped = [('data/', '① 输入数据 (现场原始件): 用户令不进包; 要一起交付用 --with-data')]
|
|
|
if (ROOT / PRODUCTS_DIR).is_dir():
|
|
if (ROOT / PRODUCTS_DIR).is_dir():
|
|
|
skipped += [('outputs/', '② 产物/输出数据: 用户令不进包 (目标机放数据后 place_raw_data.py + '
|
|
skipped += [('outputs/', '② 产物/输出数据: 用户令不进包 (目标机放数据后 place_raw_data.py + '
|
|
@@ -186,13 +207,19 @@ def plan(with_data=False, with_products=False):
|
|
|
return inc, skipped
|
|
return inc, skipped
|
|
|
|
|
|
|
|
|
|
|
|
|
-def keep(f: pathlib.Path) -> bool:
|
|
|
|
|
|
|
+def keep(f: pathlib.Path, with_data: bool = False, with_products: bool = False) -> bool:
|
|
|
"""这个文件真会被写进包吗 —— 打包循环与"预演件数"共用同一条判据。
|
|
"""这个文件真会被写进包吗 —— 打包循环与"预演件数"共用同一条判据。
|
|
|
|
|
|
|
|
★ 2026-09-16 修正: 原实现里 `size_of()` 数的是**目录下所有文件**(连 `__pycache__/*.pyc` 一起数),
|
|
★ 2026-09-16 修正: 原实现里 `size_of()` 数的是**目录下所有文件**(连 `__pycache__/*.pyc` 一起数),
|
|
|
而实际写入时又跳过它们 ⇒ 预演报的"件数/体积"比包里真的多 (src 少报 100+ 件)。
|
|
而实际写入时又跳过它们 ⇒ 预演报的"件数/体积"比包里真的多 (src 少报 100+ 件)。
|
|
|
现在两处都走 `keep()`: 预演数字 = 包里条目数, 对不上就是 bug。
|
|
现在两处都走 `keep()`: 预演数字 = 包里条目数, 对不上就是 bug。
|
|
|
★ 2026-09-17: 加"运行日志(*.log/*.jsonl 任何位置)"与"临时/备份文件"两类排除 (用户令)。
|
|
★ 2026-09-17: 加"运行日志(*.log/*.jsonl 任何位置)"与"临时/备份文件"两类排除 (用户令)。
|
|
|
|
|
+ ★ 2026-09-17 修: `--with-products` / `--with-data` 原先**是个摆设** —— `keep()` 无条件套用
|
|
|
|
|
+ `EXCLUDE_GLOBS`(里面有 `data`/`outputs`), 于是 `plan()` 把 outputs/ 加进要打清单、打印
|
|
|
|
|
+ "本次给了 --with-products, 例外打进包", 而写盘循环又把它们逐件滤掉: 实测 `--with-products`
|
|
|
|
|
+ 打出来的包与默认包**同样 3441 件 / 879 MB**, 里面一件产物都没有。后果不是"少带东西",
|
|
|
|
|
+ 而是"以为有存档、其实没有" —— 要补随包件时才发现在包里找不到。现在两个开关**真的**生效:
|
|
|
|
|
+ 例外目录一旦显式要带, 就不再被 EXCLUDE_GLOBS 拦下。
|
|
|
"""
|
|
"""
|
|
|
import fnmatch
|
|
import fnmatch
|
|
|
parts = f.relative_to(ROOT).parts
|
|
parts = f.relative_to(ROOT).parts
|
|
@@ -202,16 +229,22 @@ def keep(f: pathlib.Path) -> bool:
|
|
|
return False
|
|
return False
|
|
|
if len(parts) == 1 and f.suffix.lower() == '.zip': # 顶层旧交付包, 避免包中包
|
|
if len(parts) == 1 and f.suffix.lower() == '.zip': # 顶层旧交付包, 避免包中包
|
|
|
return False
|
|
return False
|
|
|
- return not any(x.startswith(tuple(g.rstrip('*') for g in EXCLUDE_GLOBS)) for x in parts[:1])
|
|
|
|
|
|
|
+ allow = set()
|
|
|
|
|
+ if with_data:
|
|
|
|
|
+ allow.add('data')
|
|
|
|
|
+ if with_products:
|
|
|
|
|
+ allow.add(PRODUCTS_DIR)
|
|
|
|
|
+ globs = tuple(g.rstrip('*') for g in EXCLUDE_GLOBS if g.rstrip('*') not in allow)
|
|
|
|
|
+ return not any(x.startswith(globs) for x in parts[:1])
|
|
|
|
|
|
|
|
|
|
|
|
|
-def size_of(p: pathlib.Path):
|
|
|
|
|
|
|
+def size_of(p: pathlib.Path, with_data: bool = False, with_products: bool = False):
|
|
|
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() and keep(f):
|
|
|
|
|
- n += 1; s += f.stat().st_size
|
|
|
|
|
|
|
+ if f.is_file() and keep(f, with_data, with_products):
|
|
|
|
|
+ n += 1; s = s + f.stat().st_size
|
|
|
return n, s
|
|
return n, s
|
|
|
|
|
|
|
|
|
|
|
|
@@ -221,7 +254,7 @@ def build(out: pathlib.Path, with_data=False, progress=True, with_products=False
|
|
|
print('== 要打进包的内容 ==')
|
|
print('== 要打进包的内容 ==')
|
|
|
print(' (默认口径: 不含 输入数据 / 产物 / 运行日志 / 临时文件 —— 用户令 2026-09-17)')
|
|
print(' (默认口径: 不含 输入数据 / 产物 / 运行日志 / 临时文件 —— 用户令 2026-09-17)')
|
|
|
for p in inc:
|
|
for p in inc:
|
|
|
- n, s = size_of(p)
|
|
|
|
|
|
|
+ n, s = size_of(p, with_data, with_products)
|
|
|
tot_n += n; tot_s += s
|
|
tot_n += n; tot_s += s
|
|
|
print(f' {p.relative_to(ROOT).as_posix():26s} {n:6d} 件 {s/1e6:9.1f} MB')
|
|
print(f' {p.relative_to(ROOT).as_posix():26s} {n:6d} 件 {s/1e6:9.1f} MB')
|
|
|
print(f' {"合计":26s} {tot_n:6d} 件 {tot_s/1e6:9.1f} MB')
|
|
print(f' {"合计":26s} {tot_n:6d} 件 {tot_s/1e6:9.1f} MB')
|
|
@@ -252,7 +285,7 @@ def build(out: pathlib.Path, with_data=False, progress=True, with_products=False
|
|
|
done += 1
|
|
done += 1
|
|
|
else:
|
|
else:
|
|
|
for f in p.rglob('*'):
|
|
for f in p.rglob('*'):
|
|
|
- if not f.is_file() or not keep(f):
|
|
|
|
|
|
|
+ if not f.is_file() or not keep(f, with_data, with_products):
|
|
|
continue
|
|
continue
|
|
|
z.write(f, f.relative_to(ROOT).as_posix())
|
|
z.write(f, f.relative_to(ROOT).as_posix())
|
|
|
done += 1
|
|
done += 1
|
|
@@ -272,11 +305,17 @@ def build(out: pathlib.Path, with_data=False, progress=True, with_products=False
|
|
|
print(f' [X] 包内 {e} 引用了 {rel}, 但包里没有 —— 这个包不能交付')
|
|
print(f' [X] 包内 {e} 引用了 {rel}, 但包里没有 —— 这个包不能交付')
|
|
|
for m in enc:
|
|
for m in enc:
|
|
|
print(f' [X] 入口编码守则: {m}')
|
|
print(f' [X] 入口编码守则: {m}')
|
|
|
- if miss or enc:
|
|
|
|
|
|
|
+ # 装机产物不得进包 (2026-09-17 实逮: 本机的 install-info.json 被原样打进包 ⇒ 目标机安装前检查
|
|
|
|
|
+ # 会误报"已安装 vx.y.z, 版本相同" —— 版本检查当场失灵)。这类"看着无害、后果是静默误报"的东西
|
|
|
|
|
+ # 必须由机器守: 包里出现一件就删包。
|
|
|
|
|
+ machine = [n for n in names if n.split('/')[-1] in ('install-info.json',) or n.endswith('.lnk')]
|
|
|
|
|
+ for n in machine[:5]:
|
|
|
|
|
+ print(f' [X] 包里有"装机产物" {n} —— 它只在本机有效 (安装记录/快捷方式), 不该随包分发')
|
|
|
|
|
+ if miss or enc or machine:
|
|
|
out.unlink()
|
|
out.unlink()
|
|
|
print(f' [X] 已删除不合格的包 {out.name} (留着会被人当成能用的包发出去)')
|
|
print(f' [X] 已删除不合格的包 {out.name} (留着会被人当成能用的包发出去)')
|
|
|
return 1
|
|
return 1
|
|
|
- print(f' [OK] 入口引用闭合 + 编码守则: 包内每个入口脚本引用的文件都在包里 (条目 {len(names)} 件)')
|
|
|
|
|
|
|
+ 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
|
|
@@ -381,7 +420,7 @@ def main() -> int:
|
|
|
tot_n = tot_s = 0
|
|
tot_n = tot_s = 0
|
|
|
print(f'== 会打进包 (version {VERSION}) ==')
|
|
print(f'== 会打进包 (version {VERSION}) ==')
|
|
|
for p in inc:
|
|
for p in inc:
|
|
|
- n, s = size_of(p); tot_n += n; tot_s += s
|
|
|
|
|
|
|
+ n, s = size_of(p, a.with_data, a.with_products); tot_n += n; tot_s += s
|
|
|
print(f' {p.relative_to(ROOT).as_posix():26s} {n:6d} 件 {s/1e6:9.1f} MB')
|
|
print(f' {p.relative_to(ROOT).as_posix():26s} {n:6d} 件 {s/1e6:9.1f} MB')
|
|
|
print(f' {"合计":26s} {tot_n:6d} 件 {tot_s/1e6:9.1f} MB → {out}')
|
|
print(f' {"合计":26s} {tot_n:6d} 件 {tot_s/1e6:9.1f} MB → {out}')
|
|
|
print('== 会排除 ==')
|
|
print('== 会排除 ==')
|