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-cpp 替换手写 C ABI 引导并保留执行协议 采用 MSVC 与 CMake 构建,校验依赖归档和源码缓存 修复动态卸载时的实例绑定与单例包装生命周期 补充构建缓存测试、实机验收结果及分发文档
92 lines
3.8 KiB
Python
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)
|