chore: checkpoint web Blender migration source, tools and docs
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled

Accumulated uncommitted work: GHOST Web backend, WebGL/GPU web layer,
web workflow engine and protocol, tooling, docs and task records.
Large generated evidence, user-testing archives, build snapshots and
imported test assets remain untracked.

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
mes123456
2026-11-10 11:18:01 +08:00
parent 122142c26c
commit f3e21c6b98
6887 changed files with 257154 additions and 12294 deletions

View File

@@ -0,0 +1,95 @@
#!/usr/bin/env python3
"""Build the local desktop-version CPython archive for the WebEngine memory."""
import argparse
import datetime
import hashlib
import json
import os
from pathlib import Path
import shutil
import subprocess
import zipfile
ROOT = Path(__file__).resolve().parents[2]
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--source', type=Path, default=Path('/home/mes123456/working/build_oiio_deps/build/python/src/external_python'))
args = parser.parse_args()
base = ROOT / 'build_web_python313'
source, build = base / 'source', base / 'build'
report_dir = ROOT / 'tests/golden/WBV2-P1424-001/embedded-python-build' / datetime.datetime.now(datetime.timezone.utc).strftime('%Y%m%dT%H%M%SZ')
report_dir.mkdir(parents=True)
report = dict(status='BUILDING', validation='NOT_RUN', source=str(args.source), steps=[])
try:
patchlevel = (args.source / 'Include/patchlevel.h').read_text()
if '#define PY_VERSION "3.13.13"' not in patchlevel:
raise ValueError('The embedded runtime requires the supplied Blender CPython 3.13.13 source')
report['version'] = '3.13.13'
report['sourceIdentity'] = {name: hashlib.sha256((args.source / name).read_bytes()).hexdigest()
for name in ['Include/patchlevel.h', 'configure', 'LICENSE']}
if not source.exists():
shutil.copytree(args.source, source, ignore=shutil.ignore_patterns(
'*.o', '*.a', '*.so', '*.so.*', '*.pyc', '__pycache__', '.git', 'build',
'python', 'Makefile', 'pyconfig.h', 'config.log', 'config.status', 'config.cache',
'pybuilddir.txt', '_bootstrap_python', '_freeze_module', 'Setup.local', 'Setup.stdlib', 'Setup.bootstrap', 'config.c'))
# Keep the copied upstream source reproducible without rebuilding or
# replacing the archive on every implementation cycle.
if 'web_engine_python_unraisable' not in (source / 'Python/errors.c').read_text():
subprocess.run(['patch', '-p1', '-i',
str(ROOT / 'tools/web/patches/cpython-3.13-webengine-unraisable.patch')],
cwd=source, check=True)
build.mkdir(parents=True, exist_ok=True)
build_python = ROOT / '.deps/native/python/bin/python3.13'
env = dict(os.environ)
env['CFLAGS'] = (env.get('CFLAGS', '') + ' -DPY_CALL_TRAMPOLINE=1').strip()
env['LD_LIBRARY_PATH'] = str(ROOT / '.deps/native/python/lib') + ':' + env.get('LD_LIBRARY_PATH', '')
env['PYTHONHOME'] = str(ROOT / '.deps/native/python')
def run(name, command):
with (report_dir / (name + '.log')).open('w') as log:
result = subprocess.run(command, cwd=ROOT, env=env, stdout=log, stderr=subprocess.STDOUT)
report['steps'].append(dict(name=name, exitCode=result.returncode))
if result.returncode:
raise RuntimeError(name + ' failed; see ' + str(report_dir))
if not (build / 'Makefile').exists() or 'PY_CALL_TRAMPOLINE' not in (build / 'Makefile').read_text():
# Shell arguments stay positional; source paths are never interpolated as shell code.
run('configure', ['bash', '-c',
'source tools/web/emscripten-env.sh && cd "$1" && CONFIG_SITE="$2/Tools/wasm/config.site-wasm32-emscripten" emconfigure "$2/configure" --host=wasm32-unknown-emscripten --build=x86_64-pc-linux-gnu --with-emscripten-target=browser --disable-wasm-pthreads --disable-wasm-dynamic-linking --disable-shared --without-ensurepip --disable-test-modules --prefix=/python --with-build-python="$3"',
'web-python-build', str(build), str(source), str(build_python)])
configuration = build / 'pyconfig.h'
if '#define PY_CALL_TRAMPOLINE 1' not in configuration.read_text():
configuration.write_text(configuration.read_text() + '\n#define PY_CALL_TRAMPOLINE 1\n')
frozen = build / 'Python/frozen_modules/getpath.h'
frozen.parent.mkdir(parents=True, exist_ok=True)
if not frozen.exists():
run('freeze-getpath', [str(build_python), str(source / 'Programs/_freeze_module.py'),
'getpath', str(source / 'Modules/getpath.py'), str(frozen)])
run('archive', ['bash', '-c', 'source tools/web/emscripten-env.sh && emmake make -C "$1" -j4 libpython3.13.a', 'web-python-build', str(build)])
stdlib = base / 'python313.zip'
with zipfile.ZipFile(stdlib, 'w', compression=zipfile.ZIP_STORED) as archive:
for file in sorted((source / 'Lib').rglob('*.py')):
relative = file.relative_to(source / 'Lib')
if any(part in {'test', 'tests', '__pycache__', 'idlelib', 'tkinter', 'turtledemo', 'ensurepip'} for part in relative.parts):
continue
archive.write(file, relative.as_posix())
report['artifacts'] = {}
for file in [build / 'libpython3.13.a', build / 'pyconfig.h', stdlib,
build / 'Modules/expat/libexpat.a', build / 'Modules/_hacl/libHacl_Hash_SHA2.a',
build / 'Modules/_decimal/libmpdec/libmpdec.a']:
data = file.read_bytes()
report['artifacts'][str(file.relative_to(ROOT))] = dict(bytes=len(data), sha256=hashlib.sha256(data).hexdigest())
report['status'] = 'BUILT_UNVERIFIED'
license_dir = ROOT / 'web/app/public/vendor/blender'
license_dir.mkdir(parents=True, exist_ok=True)
shutil.copy2(source / 'LICENSE', license_dir / 'CPYTHON-LICENSE.txt')
except BaseException as error:
report.update(status='BUILD_INCOMPLETE', error=str(error))
raise
finally:
(report_dir / 'build.json').write_text(json.dumps(report, indent=2) + '\n')
print(json.dumps(dict(directory=str(report_dir), **report)), flush=True)
if __name__ == '__main__':
main()