221 lines
7.2 KiB
Python
Executable File
221 lines
7.2 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
"""
|
|
Make Python wheel package (`*.whl`) file from Blender built with 'WITH_PYTHON_MODULE' enabled.
|
|
|
|
Example
|
|
=======
|
|
|
|
If the "bpy" module was build on Linux using the command:
|
|
|
|
make bpy lite
|
|
|
|
The command to package it as a wheel is:
|
|
|
|
./build_files/utils/make_bpy_wheel.py ../build_linux_bpy_lite/bin --output-dir=./
|
|
|
|
This will create a `*.whl` file in the current directory.
|
|
"""
|
|
|
|
import argparse
|
|
import make_utils
|
|
import os
|
|
import re
|
|
import platform
|
|
import string
|
|
import setuptools # type: ignore
|
|
import sys
|
|
|
|
from typing import (
|
|
Generator,
|
|
List,
|
|
Optional,
|
|
Sequence,
|
|
Tuple,
|
|
)
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# 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:
|
|
sys.stderr.write("Unable to find %r in %r, abort!\n" % (var, filepath_cmake_cache))
|
|
sys.exit(1)
|
|
return value
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Argument Parser
|
|
|
|
def argparse_create() -> argparse.ArgumentParser:
|
|
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawTextHelpFormatter)
|
|
parser.add_argument(
|
|
"install_dir",
|
|
metavar='INSTALL_DIR',
|
|
type=str,
|
|
help="The installation directory containing the \"bpy\" package.",
|
|
)
|
|
parser.add_argument(
|
|
"--build-dir",
|
|
metavar='BUILD_DIR',
|
|
default=None,
|
|
help="The build directory containing 'CMakeCache.txt' (search parent directories of INSTALL_DIR when omitted).",
|
|
required=False,
|
|
)
|
|
parser.add_argument(
|
|
"--output-dir",
|
|
metavar='OUTPUT_DIR',
|
|
default=None,
|
|
help="The destination directory for the '*.whl' file (use INSTALL_DIR when omitted).",
|
|
required=False,
|
|
)
|
|
|
|
return parser
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Main Function
|
|
|
|
def main() -> None:
|
|
|
|
# Parse arguments.
|
|
args = argparse_create().parse_args()
|
|
|
|
install_dir = os.path.abspath(args.install_dir)
|
|
output_dir = os.path.abspath(args.output_dir) if args.output_dir else install_dir
|
|
|
|
if args.build_dir:
|
|
build_dir = os.path.abspath(args.build_dir)
|
|
filepath_cmake_cache = os.path.join(build_dir, "CMakeCache.txt")
|
|
del build_dir
|
|
if not os.path.exists(filepath_cmake_cache):
|
|
sys.stderr.write("File not found %r, abort!\n" % filepath_cmake_cache)
|
|
sys.exit(1)
|
|
else:
|
|
filepath_cmake_cache = find_dominating_file(install_dir, ("CMakeCache.txt",))
|
|
if not filepath_cmake_cache:
|
|
# Should never fail.
|
|
sys.stderr.write("Unable to find CMakeCache.txt in or above %r, abort!\n" % install_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")
|
|
if glibc is None:
|
|
sys.stderr.write("Unable to find \"CS_GNU_LIBC_VERSION\", abort!\n")
|
|
sys.exit(1)
|
|
glibc = "%s_%s" % tuple(glibc.split()[1].split(".")[:2])
|
|
platform_tag = "manylinux_%s_%s" % (glibc, platform.machine().lower())
|
|
else:
|
|
sys.stderr.write("Unsupported platform: %s, abort!\n" % (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): # type: ignore
|
|
def has_ext_modules(self) -> bool:
|
|
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()
|