Files
kdletters fdc48aa573
Project CI / AI game creator shell Rust shard 1/4 (push) Failing after 17s
Project CI / AI game creator shell Rust shard 4/4 (push) Failing after 18s
Project CI / AI game creator shell Rust shard 3/4 (push) Failing after 18s
Project CI / AI game creator shell Rust shard 2/4 (push) Failing after 18s
Project CI / Backend tests (push) Failing after 17s
Project CI / AI game creator shell Rust smoke (push) Failing after 19s
Project CI / Native shell tests (push) Failing after 18s
Project CI / AI game creator shell Rust crates (push) Failing after 18s
Project CI / Frontend tests (push) Failing after 6s
Project CI / AI game creator shell web tests (push) Failing after 11s
Project CI / Repository checks (push) Failing after 11s
将 Godot 原生引导迁移到官方 C++ 绑定
使用固定版本 godot-cpp 替换手写 C ABI 引导并保留执行协议
采用 MSVC 与 CMake 构建,校验依赖归档和源码缓存
修复动态卸载时的实例绑定与单例包装生命周期
补充构建缓存测试、实机验收结果及分发文档
2026-09-20 22:00:44 +08:00

92 lines
3.8 KiB
Python

"""Prepare only the pinned official SDK; never execute an unchecked archive."""
import hashlib
import json
import os
from pathlib import Path, PurePosixPath
import re
import stat
import sys
import tempfile
import urllib.request
import zipfile
def plain(path):
for item in (path, *path.parents):
if item.exists() or item.is_symlink():
info = item.lstat()
if item.is_symlink() or getattr(info, "st_file_attributes", 0) & 0x400:
raise ValueError(f"Dependency path cannot contain links: {item}")
def digest(data):
return hashlib.sha256(data).hexdigest()
def prepare(root):
provenance = json.loads((root / "vendor/provenance.json").read_text(encoding="utf-8"))
commit = provenance["commit"]
expected = provenance["archiveSha256"]
if not re.fullmatch(r"[a-f0-9]{40}", commit) or not re.fullmatch(r"[a-f0-9]{64}", expected):
raise ValueError("Invalid pinned dependency identity")
url = f"https://codeload.github.com/godotengine/godot-cpp/zip/{commit}"
if provenance["archiveUrl"] != url:
raise ValueError("Dependency URL must identify the pinned official repository")
cache = root / ".build/dependencies"
plain(cache)
cache.mkdir(parents=True, exist_ok=True)
archive = cache / f"{commit}.zip"
plain(archive)
if not archive.exists():
with urllib.request.urlopen(url, timeout=60) as response:
data = response.read(32 * 1024 * 1024 + 1)
if len(data) > 32 * 1024 * 1024 or digest(data) != expected:
raise ValueError("Official godot-cpp archive SHA256 mismatch")
with tempfile.NamedTemporaryFile(dir=cache, delete=False) as output:
output.write(data)
temporary = Path(output.name)
os.replace(temporary, archive)
if digest(archive.read_bytes()) != expected:
raise ValueError("Cached godot-cpp archive SHA256 mismatch; cache was preserved")
source = cache / f"godot-cpp-{commit}"
plain(source)
source.mkdir(exist_ok=True)
expected_files = set()
with zipfile.ZipFile(archive) as bundle:
for entry in bundle.infolist():
relative = PurePosixPath(entry.filename)
if (not relative.parts or relative.parts[0] != source.name or relative.is_absolute()
or ".." in relative.parts or "\\" in entry.filename or ":" in entry.filename):
raise ValueError("Unsafe dependency archive entry")
if stat.S_ISLNK(entry.external_attr >> 16):
raise ValueError("Dependency archive cannot contain symbolic links")
if entry.is_dir():
continue
target = source.joinpath(*relative.parts[1:])
if target in expected_files:
raise ValueError("Duplicate dependency archive entry")
plain(target)
expected_files.add(target)
content = bundle.read(entry)
if target.exists():
if not target.is_file() or target.read_bytes() != content:
raise ValueError(f"Modified godot-cpp source cache was preserved: {target}")
else:
target.parent.mkdir(parents=True, exist_ok=True)
target.write_bytes(content)
for item in source.rglob("*"):
plain(item)
if item.is_file() and item not in expected_files:
raise ValueError(f"Unexpected dependency source cache entry: {item}")
if (source / "LICENSE.md").read_text(encoding="utf-8") != (root / "vendor/LICENSE.txt").read_text(encoding="utf-8"):
raise ValueError("Packaged godot-cpp license differs from pinned upstream license")
return source
if __name__ == "__main__":
try:
print(prepare(Path(__file__).resolve().parent))
except (OSError, ValueError, KeyError, zipfile.BadZipFile) as error:
print(str(error), file=sys.stderr)
sys.exit(1)