-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpack.py
More file actions
254 lines (214 loc) · 9.71 KB
/
pack.py
File metadata and controls
254 lines (214 loc) · 9.71 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
"""按 chrome.release 从构建目录收集文件并打 ZIP。"""
import configparser
import glob
import hashlib
import sys
import zipfile
from pathlib import Path
def _sha256_file(path: Path) -> str:
"""计算文件的 SHA256,返回十六进制小写字符串。"""
h = hashlib.sha256()
with open(path, "rb") as f:
for chunk in iter(lambda: f.read(65536), b""):
h.update(chunk)
return h.hexdigest()
def _file_size(path: Path) -> int:
"""返回文件大小(字节)。"""
return path.stat().st_size
# 与客户端(Tauri)约定一致:只对文件前 10MB 计算 SHA256 作为签名
_SIGNATURE_HASH_SIZE = 10 * 1024 * 1024
def _sha256_file_head(path: Path, size: int = _SIGNATURE_HASH_SIZE) -> str:
"""计算文件前 size 字节的 SHA256,返回十六进制小写字符串。"""
h = hashlib.sha256()
remaining = size
with open(path, "rb") as f:
while remaining > 0:
chunk = f.read(min(65536, remaining))
if not chunk:
break
h.update(chunk)
remaining -= len(chunk)
return h.hexdigest()
# VC++ 运行时 DLL:chrome.release 未列出,但构建输出中常有,缺了会触发「并行配置不正确」。
# 若构建目录中存在则一并打入版本目录,便于解压即用。vcruntime140 / vcruntime140_1 不存在也可正常运行。
VC_RUNTIME_DLLS = [
"msvcp140.dll",
"msvcp140_atomic_wait.dll",
]
# DirectX Shader Compiler DLL:WebGPU 等功能需要,若构建目录中存在则一并打入版本目录。
DX_SHADER_DLLS = [
"dxcompiler.dll",
"dxil.dll",
]
def _insensiglob(pattern: str) -> list[str]:
"""大小写不敏感 glob(与 Chromium create_installer_archive 一致)。"""
def recase(c: str) -> str:
return f"[{c.lower()}{c.upper()}]" if c.isalpha() else c
return glob.glob("".join(recase(c) for c in pattern))
def _make_version_manifest(version: str) -> str:
"""生成版本目录用 SxS manifest(与 //chrome/app/version_assembly 模板一致)。"""
return f"""<assembly
xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'>
<assemblyIdentity
name='{version}'
version='{version}'
type='win32'/>
<file name='chrome_elf.dll'/>
</assembly>
"""
def _read_version(chromium_root: Path) -> str:
"""从 chrome/VERSION 读取 MAJOR.MINOR.BUILD.PATCH。"""
vf = chromium_root / "chrome" / "VERSION"
if not vf.is_file():
return "0.0.0.0"
major = minor = build = patch = "0"
for line in vf.read_text(encoding="utf-8").splitlines():
line = line.strip()
if line.startswith("MAJOR="):
major = line[6:].strip()
elif line.startswith("MINOR="):
minor = line[6:].strip()
elif line.startswith("BUILD="):
build = line[6:].strip()
elif line.startswith("PATCH="):
patch = line[6:].strip()
return f"{major}.{minor}.{build}.{patch}"
def get_version(chromium_root: Path) -> str:
"""公开:从 chrome/VERSION 读取版本号。"""
return _read_version(chromium_root)
def _source_pattern(option: str, executable_name: str) -> str:
"""release 里写 chrome.exe,构建产出是 executable_name.exe;仅对 exe 做映射以在构建目录定位文件。dll 由 kernel 保持 chrome.dll,此处不映射。"""
if option == "chrome.exe":
return f"{executable_name}.exe"
return option
def _collect_files(
build_dir: Path,
release_file: Path,
version: str,
executable_name: str,
sections: list[str],
package_dir_name: str,
) -> list[tuple[Path, str]]:
"""(源文件绝对路径, zip 内路径) 列表。zip 内路径用 /。"""
version_dir = f"{package_dir_name}/{version}"
config = configparser.ConfigParser(
defaults={"ChromeDir": package_dir_name, "VersionDir": version_dir}
)
config.read(release_file, encoding="utf-8")
result: list[tuple[Path, str]] = []
for section in sections:
if not config.has_section(section):
continue
for option in config.options(section):
if option.endswith("dir"):
continue
src_pattern = _source_pattern(option, executable_name)
src_subdir = src_pattern.replace("\\", "/")
src_full = build_dir / src_subdir
# 单文件
if "*" not in src_subdir:
if src_full.is_file():
dest_dir = config.get(section, option).replace("\\", "/").rstrip("/")
arc = f"{dest_dir}/{src_full.name}"
result.append((src_full.resolve(), arc))
continue
# 通配:Windows 下 Path 含反斜杠,glob 需正斜杠或显式按目录处理
dest_dir = config.get(section, option).replace("\\", "/").rstrip("/")
# locales/*.pak 必须打入 VersionDir/Locales,缺了 exe 可能无日志静默退出
if "locales" in src_subdir.lower() and "*.pak" in src_subdir.lower():
locales_dir = build_dir / "locales"
if locales_dir.is_dir():
for p in sorted(locales_dir.glob("*.pak")):
if p.is_file():
result.append((p.resolve(), f"{dest_dir}/{p.name}"))
else:
pass # 下方统一检查并警告
else:
pattern_str = str(src_full).replace("\\", "/")
for src_path in _insensiglob(pattern_str):
p = Path(src_path)
if not p.is_file():
continue
result.append((p.resolve(), f"{dest_dir}/{p.name}"))
# 将构建目录中存在的 VC 运行时 DLL 打入版本目录,避免解压后出现「并行配置不正确」
for dll_name in VC_RUNTIME_DLLS:
src_full = build_dir / dll_name
if src_full.is_file():
arc = f"{version_dir}/{src_full.name}"
result.append((src_full.resolve(), arc))
# 将构建目录中存在的 DirectX Shader Compiler DLL 打入版本目录(WebGPU 等功能需要)
for dll_name in DX_SHADER_DLLS:
src_full = build_dir / dll_name
if src_full.is_file():
arc = f"{version_dir}/{src_full.name}"
result.append((src_full.resolve(), arc))
# 版本目录清单 <版本>.manifest:exe 的 SxS 会在此目录查找,缺了会「并行配置不正确」
# 优先从构建目录打入(Chromium 由 //chrome/app/version_assembly 生成到 root_build_dir)
manifest_name = f"{version}.manifest"
manifest_src = build_dir / manifest_name
if manifest_src.is_file():
result.append((manifest_src.resolve(), f"{version_dir}/{manifest_name}"))
return result
def _strip_top_dir(arc: str, top_dir: str) -> str:
"""去掉 arc 路径开头的 top_dir/,实现 ZIP 根目录不嵌套。"""
prefix = top_dir.rstrip("/") + "/"
if arc.startswith(prefix):
return arc[len(prefix) :]
return arc
def create_zip(
build_dir: Path,
release_file: Path,
chromium_root: Path,
executable_name: str,
sections: list[str],
output_path: Path,
package_dir_name: str,
flatten_top_dir: bool = False,
verbose: bool = False,
) -> None:
"""根据 release 文件将构建产物打成 ZIP。flatten_top_dir=True 时 ZIP 根目录直接为 exe 与版本目录,无顶层 PACKAGE_DIR。"""
version = _read_version(chromium_root)
version_dir = f"{package_dir_name}/{version}"
version_manifest_arc = f"{version_dir}/{version}.manifest"
files = _collect_files(
build_dir, release_file, version, executable_name, sections, package_dir_name
)
# 若构建目录未产出 <版本>.manifest,则生成最小 manifest 并打入版本目录
has_version_manifest = any(arc == version_manifest_arc for _, arc in files)
generated_manifest = None if has_version_manifest else _make_version_manifest(version)
# Locales 为空会导致 exe 无日志、无进程静默退出,必须至少包含 en-US.pak
has_locales = any("/Locales/" in arc or "\\Locales\\" in arc for _, arc in files)
if not has_locales and "GENERAL" in sections:
print(
"警告: 未收集到任何 locales/*.pak(如 en-US.pak),解压后 Locales 目录将为空,exe 可能无法启动且无日志。",
file=sys.stderr,
)
print(
f" 请确认构建目录下存在 {build_dir / 'locales'} 且含 .pak 文件。",
file=sys.stderr,
)
if flatten_top_dir:
files = [(src, _strip_top_dir(arc, package_dir_name)) for src, arc in files]
version_manifest_arc = _strip_top_dir(version_manifest_arc, package_dir_name)
output_path = output_path.resolve()
output_path.parent.mkdir(parents=True, exist_ok=True)
with zipfile.ZipFile(output_path, "w", zipfile.ZIP_DEFLATED) as zf:
for src, arc in files:
zf.write(src, arc)
if verbose:
print(arc)
if generated_manifest is not None:
zf.writestr(version_manifest_arc, generated_manifest)
if verbose:
print(version_manifest_arc)
count = len(files) + (1 if generated_manifest else 0)
print(f"已生成: {output_path} ({count} 个文件)")
archive_size = _file_size(output_path)
archive_sha256 = _sha256_file(output_path)
print(f"archive file_size: {archive_size}")
print(f"archive sha256: {archive_sha256}")
# 输出 chrome.dll 的签名哈希(前 10MB SHA256),便于校验
for src, arc in files:
if arc.replace("\\", "/").endswith("chrome.dll"):
print(f"chrome.dll signature (head {_SIGNATURE_HASH_SIZE // 1024 // 1024}MB SHA256): {_sha256_file_head(src)}")
break