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>
96 lines
5.9 KiB
Python
96 lines
5.9 KiB
Python
#!/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()
|