移除 Docker 构建路线,改为 host 直装 Qt 5.14.2 + Qt Creator 4.11.0
学生主要用 Windows,Docker 方案不再需要:删掉 tools/docker/ 及 README/HANDOFF/ CMakeLists 里的相关引用(含一处已失效的 docker_run.sh 死链接)。 install_qt_host.sh 现在同时装期号匹配的 Qt Creator 4.11.0:官方 .run 安装器 强制要求 Qt 账号登录且无 Skip 选项,改用 tools/extract_qtcreator_payload.py 直接从 .run 里定位、解出内嵌的 7z payload,绕开账号登录。提取逻辑先解到 staging 目录再原子替换到位,避免中途失败留下部分安装但被判定为"已装"; 候选 7z 复用同一临时文件,不会在磁盘上堆多份安装包大小的临时拷贝。
This commit is contained in:
Executable
+81
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Extract the Qt Creator payload from a QtIFW .run installer without running
|
||||
its GUI. The official .run for Qt Creator (this vintage) hard-gates the wizard
|
||||
behind a mandatory Qt account login with no Skip option, so the GUI/script
|
||||
automation route is a dead end. The installer binary is a self-extracting
|
||||
QtIFW archive: an ELF stub with one or more embedded 7z streams appended.
|
||||
The real payload (bin/, lib/, share/, libexec/) lives in one of those streams;
|
||||
this scans for the 7z magic, tries each occurrence, and keeps the one whose
|
||||
contents include bin/qtcreator.
|
||||
|
||||
Usage: extract_qtcreator_payload.py <installer.run> <dest_dir>
|
||||
"""
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
import py7zr
|
||||
|
||||
MAGIC = b"7z\xbc\xaf\x27\x1c"
|
||||
|
||||
|
||||
def find_offsets(data: bytes) -> list[int]:
|
||||
offsets = []
|
||||
start = 0
|
||||
while True:
|
||||
i = data.find(MAGIC, start)
|
||||
if i == -1:
|
||||
break
|
||||
offsets.append(i)
|
||||
start = i + 1
|
||||
return offsets
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if len(sys.argv) != 3:
|
||||
print(__doc__)
|
||||
return 2
|
||||
run_path = Path(sys.argv[1])
|
||||
dest_dir = Path(sys.argv[2])
|
||||
|
||||
data = run_path.read_bytes()
|
||||
offsets = find_offsets(data)
|
||||
if not offsets:
|
||||
print("no 7z signature found in installer", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
candidate = Path(tmp) / "candidate.7z"
|
||||
staging = Path(tmp) / "staging"
|
||||
for off in offsets:
|
||||
# Overwrite the same candidate file each iteration so failed
|
||||
# attempts don't accumulate on disk (offsets can be ~installer-size apart).
|
||||
candidate.write_bytes(data[off:])
|
||||
try:
|
||||
with py7zr.SevenZipFile(candidate, mode="r") as z:
|
||||
names = z.getnames()
|
||||
except Exception:
|
||||
continue
|
||||
if "bin/qtcreator" not in names:
|
||||
continue
|
||||
print(f">> payload found at offset {off} ({len(names)} entries)")
|
||||
# Extract into a staging dir first and only move it into dest_dir
|
||||
# once extraction fully succeeds, so a crash/disk-full mid-extract
|
||||
# never leaves a partially-populated dest_dir that later runs
|
||||
# would mistake for a complete install.
|
||||
with py7zr.SevenZipFile(candidate, mode="r") as z:
|
||||
z.extractall(path=staging)
|
||||
if dest_dir.exists():
|
||||
shutil.rmtree(dest_dir)
|
||||
dest_dir.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.move(str(staging), str(dest_dir))
|
||||
print(f">> extracted to {dest_dir}")
|
||||
return 0
|
||||
|
||||
print("no offset yielded a payload containing bin/qtcreator", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user