#!/usr/bin/env python3 """Build pinned native image codecs and OpenImageIO for Blender WASM.""" import hashlib, re, subprocess, sys, tarfile, urllib.request from pathlib import Path ROOT = Path(__file__).resolve().parents[2] BASE = ROOT / 'build_web_oiio' PREFIX = BASE / 'install' VERSIONS = (ROOT / 'blender-5.2.0/build_files/build_environment/cmake/versions.cmake').read_text() def value(key): return re.search(r'set\(' + key + r' ([^)]+)\)', VERSIONS)[1] def source(name): version = value(name + '_VERSION') filename = value(name + '_FILE').replace('${' + name + '_VERSION}', version) url = value(name + '_URI').replace('${' + name + '_VERSION}', version).replace('http:', 'https:') expected, algorithm = value(name+'_HASH'), value(name+'_HASH_TYPE').lower() if name == 'PNG' and version == '1.6.58': filename = 'libpng-3061454d980de7d53608f594194cfac722721d2a.tar.gz' url = 'https://codeload.github.com/pnggroup/libpng/tar.gz/3061454d980de7d53608f594194cfac722721d2a' expected, algorithm = 'a2569b7553971e36e86c8f75b9d1b5abbe92c436e05169afe3f0072e937f3536', 'sha256' archive = BASE / filename if not archive.exists(): try: with urllib.request.urlopen(url, timeout=60) as response: data = response.read() except OSError: with urllib.request.urlopen('https://download.blender.org/dependencies/' + filename, timeout=60) as response: data = response.read() if hashlib.new(algorithm, data).hexdigest() != expected: raise SystemExit(name + ' download differs from Blender pinned hash') archive.write_bytes(data) if hashlib.new(algorithm, archive.read_bytes()).hexdigest() != expected: raise SystemExit(name + ' archive hash mismatch') with tarfile.open(archive) as package: directory = BASE / package.getmembers()[0].name.split('/')[0] if not (directory / 'CMakeLists.txt').exists(): package.extractall(BASE, filter='data') return directory def build(name, directory, options): if len(sys.argv)>1 and name not in sys.argv[1:]: return toolchain = ROOT / '.emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake' prefixes = ';'.join(str(p) for p in (PREFIX, ROOT/'build_web_openexr/install', ROOT/'build_web_ocio/install')) subprocess.run(['cmake', '-S', str(directory), '-B', str(BASE/name), '-G', 'Ninja', '-DCMAKE_TOOLCHAIN_FILE='+str(toolchain), '-DCMAKE_BUILD_TYPE=Release', '-DCMAKE_INSTALL_PREFIX='+str(PREFIX), '-DCMAKE_INSTALL_LIBDIR='+str(PREFIX/'lib'), '-DCMAKE_PREFIX_PATH='+prefixes, '-DCMAKE_FIND_ROOT_PATH='+prefixes, '-DCMAKE_CXX_FLAGS=-fexceptions', '-DBUILD_SHARED_LIBS=OFF', '-DBUILD_TESTING=OFF', '-DZLIB_LIBRARY='+str(ROOT/'build_web_blender6/lib/libzstatic.a'), '-DZLIB_INCLUDE_DIR='+str(ROOT/'blender-5.2.0/extern/zlib'), *options], cwd=ROOT, check=True) subprocess.run(['cmake','--build',str(BASE/name),'--target','install','--parallel','8'],cwd=ROOT,check=True) def main(): BASE.mkdir(exist_ok=True) build('jpeg',source('JPEG'),['-DENABLE_SHARED=OFF','-DENABLE_STATIC=ON','-DWITH_SIMD=OFF','-DENABLE_TESTING=OFF']) build('png',source('PNG'),['-DPNG_SHARED=OFF','-DPNG_TESTS=OFF','-DPNG_TOOLS=OFF','-DPNG_EXECUTABLES=OFF']) build('tiff',source('TIFF'),['-Dtiff-tools=OFF','-Dtiff-tests=OFF','-Dtiff-contrib=OFF','-Dtiff-docs=OFF','-Dtiff-deprecated=OFF','-Dlibdeflate=OFF','-Dlzma=OFF','-Dzstd=OFF','-Dwebp=OFF','-Djbig=OFF']) robin = source('ROBINMAP') fmt = BASE/'fmt'; fmt.mkdir(exist_ok=True) (fmt/'fmtConfigVersion.cmake').write_text('set(PACKAGE_VERSION "10.1.0")\nset(PACKAGE_VERSION_COMPATIBLE TRUE)\n') (fmt/'fmtConfig.cmake').write_text('add_library(fmt::fmt-header-only INTERFACE IMPORTED)\nset_target_properties(fmt::fmt-header-only PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "'+str(ROOT/'blender-5.2.0/extern/fmt/include')+'" INTERFACE_COMPILE_DEFINITIONS "FMT_HEADER_ONLY;FMT_CONSTEVAL=;FMT_USE_CONSTEXPR=0;FMT_USE_NONTYPE_TEMPLATE_ARGS=0")\n') oiio=source('OPENIMAGEIO') platform=oiio/'src/libutil/sysutil.cpp'; text=platform.read_text() text=text.replace('#include ', '#include \n#ifdef __EMSCRIPTEN__\n# include \n#endif',1) if '#ifdef __EMSCRIPTEN__' not in text else text text=text.replace('#elif defined(__GNU__) || defined(__OpenBSD__) || defined(_WIN32)', '#elif defined(__GNU__) || defined(__OpenBSD__) || defined(_WIN32) || defined(__EMSCRIPTEN__)') text=text.replace('# include ', '# include \n# include ',1) if '' not in text else text text=text.replace('Sysutil::physical_memory()\n{\n#if defined(__linux__)', 'Sysutil::physical_memory()\n{\n#if defined(__EMSCRIPTEN__)\n return emscripten_get_heap_max();\n#elif defined(__linux__)') platform.write_text(text) strings=oiio/'src/libutil/strutil.cpp'; text=strings.read_text() text=text.replace('|| defined(__GLIBC__)\n', '|| defined(__GLIBC__) || defined(__EMSCRIPTEN__)\n') strings.write_text(text.replace('|| defined(__GLIBC__) || defined(__NetBSD__)\n', '|| defined(__GLIBC__) || defined(__NetBSD__) || defined(__EMSCRIPTEN__)\n')) build('oiio',oiio,[ '-DCMAKE_PROJECT_INCLUDE='+str(ROOT/'tools/web/oiio-wasm-dependencies.cmake'), '-Dfmt_DIR='+str(fmt), '-DROBINMAP_INCLUDE_DIR='+str(robin/'include'), '-DOIIO_BUILD_TOOLS=OFF','-DOIIO_BUILD_TESTS=OFF','-DUSE_PYTHON=OFF','-DINSTALL_DOCS=OFF','-DEMBEDPLUGINS=ON', '-DOpenImageIO_BUILD_MISSING_DEPS=none','-DUSE_SIMD=0','-DUSE_TBB=OFF','-DUSE_OPENCOLORIO=ON', '-DUSE_FFMPEG=OFF','-DUSE_OPENVDB=OFF','-DUSE_FREETYPE=OFF','-DUSE_OPENCV=OFF','-DUSE_OPENJPEG=OFF','-DUSE_WEBP=OFF', '-DUSE_JXL=OFF','-DUSE_LIBHEIF=OFF','-DUSE_LIBRAW=OFF','-DUSE_LIBUHDR=OFF','-DUSE_PTEX=OFF','-DUSE_DCMTK=OFF']) if __name__ == '__main__': main()