From 9f76d0c8e6dd2570e0da1763fcadfb738add4be2 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Thu, 15 Sep 2022 18:01:15 +0200 Subject: Python: script for packing bpy module as wheel The buildbot will call this script to create a binary .whl file that can be easily installed through pip. This wheel will only work with the same Python version used for Blender. Other minimum system requirements are the same as regular Blender builds. Includes contributions by Campbell Barton. Differential Revision: https://developer.blender.org/D15957 --- build_files/cmake/platform/platform_win32.cmake | 4 +- build_files/utils/make_bpy_wheel.py | 167 ++++++++++++++++++++++++ 2 files changed, 170 insertions(+), 1 deletion(-) create mode 100755 build_files/utils/make_bpy_wheel.py diff --git a/build_files/cmake/platform/platform_win32.cmake b/build_files/cmake/platform/platform_win32.cmake index 771b00a3d09..dd5d04d7c92 100644 --- a/build_files/cmake/platform/platform_win32.cmake +++ b/build_files/cmake/platform/platform_win32.cmake @@ -504,7 +504,9 @@ if(WITH_JACK) endif() if(WITH_PYTHON) - set(PYTHON_VERSION 3.10) # CACHE STRING) + # Cache version for make_bpy_wheel.py to detect. + unset(PYTHON_VERSION CACHE) + set(PYTHON_VERSION "3.10" CACHE STRING "Python version") string(REPLACE "." "" _PYTHON_VERSION_NO_DOTS ${PYTHON_VERSION}) set(PYTHON_LIBRARY ${LIBDIR}/python/${_PYTHON_VERSION_NO_DOTS}/libs/python${_PYTHON_VERSION_NO_DOTS}.lib) diff --git a/build_files/utils/make_bpy_wheel.py b/build_files/utils/make_bpy_wheel.py new file mode 100755 index 00000000000..e98e8570212 --- /dev/null +++ b/build_files/utils/make_bpy_wheel.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-or-later + +import argparse +import make_utils +import os +import re +import platform +import string +import setuptools +import sys + +from typing import ( + Generator, + Tuple, + List, + Optional, + Sequence, +) + + +# ------------------------------------------------------------------------------ +# Generic Functions + +def find_dominating_file( + path: str, + search: Sequence[str], +) -> str: + while True: + for d in search: + if os.path.exists(os.path.join(path, d)): + return os.path.join(path, d) + path_next = os.path.normpath(os.path.join(path, "..")) + if path == path_next: + break + path = path_next + return "" + + +# ------------------------------------------------------------------------------ +# CMake Cache Access + +def cmake_cache_var_iter(filepath_cmake_cache: str) -> Generator[Tuple[str, str, str], None, None]: + import re + re_cache = re.compile(r"([A-Za-z0-9_\-]+)?:?([A-Za-z0-9_\-]+)?=(.*)$") + with open(filepath_cmake_cache, "r", encoding="utf-8") as cache_file: + for l in cache_file: + match = re_cache.match(l.strip()) + if match is not None: + var, type_, val = match.groups() + yield (var, type_ or "", val) + + +def cmake_cache_var(filepath_cmake_cache: str, var: str) -> Optional[str]: + for var_iter, type_iter, value_iter in cmake_cache_var_iter(filepath_cmake_cache): + if var == var_iter: + return value_iter + return None + + +def cmake_cache_var_or_exit(filepath_cmake_cache: str, var: str) -> str: + value = cmake_cache_var(filepath_cmake_cache, var) + if value is None: + print("Unable to find %r exiting!" % var) + sys.exit(1) + return value + + +# ------------------------------------------------------------------------------ +# Main Function + +def main() -> None: + + # Parse arguments. + parser = argparse.ArgumentParser(description="Make Python wheel package") + parser.add_argument("install_dir") + parser.add_argument("--build-dir", default=None) + parser.add_argument("--output-dir", default=None) + args = parser.parse_args() + + install_dir = os.path.abspath(args.install_dir) + build_dir = os.path.abspath(args.build_dir) if args.build_dir else install_dir + output_dir = os.path.abspath(args.output_dir) if args.output_dir else install_dir + + filepath_cmake_cache = find_dominating_file(build_dir, ("CMakeCache.txt",)) + if not filepath_cmake_cache: + # Should never fail. + print("Unable to find CMakeCache.txt in or above %r" % (build_dir)) + sys.exit(1) + + # Get the major and minor Python version. + python_version = cmake_cache_var_or_exit(filepath_cmake_cache, "PYTHON_VERSION") + python_version_number = ( + tuple(int("".join(c for c in digit if c in string.digits)) for digit in python_version.split(".")) + + # Support version without a minor version "3" (add zero). + tuple((0, 0, 0)) + ) + python_version_str = "%d.%d" % python_version_number[:2] + + # Get Blender version. + blender_version_str = str(make_utils.parse_blender_version()) + + # Set platform tag following conventions. + if sys.platform == "darwin": + target = cmake_cache_var_or_exit(filepath_cmake_cache, "CMAKE_OSX_DEPLOYMENT_TARGET").split(".") + machine = cmake_cache_var_or_exit(filepath_cmake_cache, "CMAKE_OSX_ARCHITECTURES") + platform_tag = "macosx_%d_%d_%s" % (int(target[0]), int(target[1]), machine) + elif sys.platform == "win32": + platform_tag = "win_%s" % (platform.machine().lower()) + elif sys.platform == "linux": + glibc = os.confstr("CS_GNU_LIBC_VERSION").split()[1].split(".") + platform_tag = "manylinux_%s_%s_%s" % (glibc[0], glibc[1], platform.machine().lower()) + else: + print("Unsupported platform %s" % (sys.platform)) + sys.exit(1) + + os.chdir(install_dir) + + # Include all files recursively. + def package_files(root_dir: str) -> List[str]: + paths = [] + for path, dirs, files in os.walk(root_dir): + paths += [os.path.join("..", path, f) for f in files] + return paths + + # Ensure this wheel is marked platform specific. + class BinaryDistribution(setuptools.dist.Distribution): + def has_ext_modules(foo): + return True + + # Build wheel. + sys.argv = [sys.argv[0], "bdist_wheel"] + + setuptools.setup( + name="bpy", + version=blender_version_str, + install_requires=["cython", "numpy", "requests", "zstandard"], + python_requires="==%d.%d.*" % (python_version_number[0], python_version_number[1]), + packages=["bpy"], + package_data={"": package_files("bpy")}, + distclass=BinaryDistribution, + options={"bdist_wheel": {"plat_name": platform_tag}}, + + description="Blender as a Python module", + license="GPL-3.0", + author="Blender Foundation", + author_email="bf-committers@blender.org", + url="https://www.blender.org" + ) + + if not os.path.exists(output_dir): + os.makedirs(output_dir) + + # Move wheel to output directory. + dist_dir = os.path.join(install_dir, "dist") + for f in os.listdir(dist_dir): + if f.endswith(".whl"): + # No apparent way to override this ABI version with setuptools, so rename. + sys_py = "cp%d%d" % (sys.version_info.major, sys.version_info.minor) + blender_py = "cp%d%d" % (python_version_number[0], python_version_number[1]) + renamed_f = f.replace(sys_py, blender_py) + + os.rename(os.path.join(dist_dir, f), os.path.join(output_dir, renamed_f)) + + +if __name__ == "__main__": + main() -- cgit v1.2.3