#!/usr/bin/env python import subprocess import os import sys import time import shutil import argparse from typing import Optional, Iterable PYTHON_PATH = sys.executable def print_error(*s: str): print("\033[91m {}\033[00m".format(' '.join(s))) def print_succes(*s: str): print("\033[92m {}\033[00m".format(' '.join(s))) def print_info(*s: str): print("\033[93m {}\033[00m".format(' '.join(s))) def build_type_mismatch(debug: bool) -> bool: has_mismatch = False if os.path.exists("build/build.cache"): with open("build/build.cache", 'rb') as f: f.seek(0, 2) pos = f.tell() if pos: f.seek(0) cur_mode = b'\x00' if debug else b'\x01' old_mode = f.read(1) if old_mode != cur_mode: has_mismatch = True else: has_mismatch = True else: has_mismatch = True with open("build/build.cache", 'wb') as f: f.write(b'\x00' if debug else b'\x01') return has_mismatch def clean_build_data(ext_dirs: Iterable[str]): for ext_dir in ext_dirs: shutil.rmtree(os.path.join(ext_dir, "build"), ignore_errors=True) def create_distribution(addon_root_path: str, dist_path: str): print_info(f'\nCreating WBS distribution in \"{dist_path}\" ...') os.makedirs(dist_path, exist_ok=True) final_dist_path = os.path.join(dist_path, 'io_scene_wmo') os.makedirs(final_dist_path, exist_ok=True) shutil.copytree(addon_root_path, final_dist_path, dirs_exist_ok=True) paths_to_remove = ( "__pycache__", "build", "venv", "build.py", ".idea", ".vs", ".vscode", ".gitignore", "requirements.txt", "wbs_kernel/build", "wbs_kernel/cmake-build-debug", "wbs_kernel/cmake-build-release", "wbs_kernel/.idea", "wbs_kernel/.vs", "wbs_kernel/.vscode", "wbs_kernel/CmakeLists.txt", "wbs_kernel/setup.py", "wbs_kernel/src", "pywowlib/.git", "pywowlib/.gitignore", "pywowlib/.gitmodules", "pywowlib/.travis.yml", "pywowlib/build.py", "pywowlib/cmake.py", "pywowlib/requirements.txt", "pywowlib/__pycache__", "pywowlib/archives/casc/build", "pywowlib/archives/casc/__pycache__", "pywowlib/archives/casc/include", "pywowlib/archives/casc/CASCLib", "pywowlib/archives/casc/lib", "pywowlib/archives/casc/casc.cpp", "pywowlib/archives/casc/setup.py", "pywowlib/archives/casc/cmake.py", "pywowlib/archives/casc/requirements.txt", "pywowlib/archives/casc/.git", "pywowlib/archives/casc/.gitignore", "pywowlib/archives/casc/.gitmodules", "pywowlib/archives/mpq/native/build", "pywowlib/archives/mpq/native/include", "pywowlib/archives/mpq/native/lib", "pywowlib/archives/mpq/native/StormLib", "pywowlib/archives/mpq/native/python_wrapper.hpp", "pywowlib/archives/mpq/native/stormmodule.cc", "pywowlib/archives/mpq/native/stormmodule.h", "pywowlib/archives/mpq/native/setup.py", "pywowlib/blp/include", "pywowlib/blp/BLP2PNG/build", "pywowlib/blp/BLP2PNG/native", "pywowlib/blp/BLP2PNG/blp.cpp", "pywowlib/blp/BLP2PNG/test.py", "pywowlib/blp/BLP2PNG/setup.py", "pywowlib/blp/PNG2BLP/build", "pywowlib/blp/PNG2BLP/native", "pywowlib/blp/PNG2BLP/png2blp.cpp", "pywowlib/blp/PNG2BLP/setup.py", ) for path in paths_to_remove: new_path = os.path.join(final_dist_path, path) if not os.path.exists(new_path): continue if os.path.isdir(new_path): shutil.rmtree(new_path, ignore_errors=True) else: os.remove(new_path) print_succes("\nSuccessfully created WBS distribution.") def build_project(debug: bool, clean: bool, no_req: bool, dist_path: Optional[str]): start_time = time.time() print_info('\nBuilding WoW Blender Studio...') print('Detected build parameters:') print(f'Build type: {"Debug" if debug else "Release"}') print(f'Build mode: {"Clean" if clean else "Incremental"}') print(f'Python third-party modules: {"OFF" if no_req else "ON"}') do_distribute = bool(dist_path) try: import Cython except ImportError: print_error("\nCython is required to build this project") sys.exit(1) try: from pip import main as pipmain except ImportError: try: from pip._internal import main as pipmain except ImportError: print_error("\npip is required to build this project.") sys.exit(1) addon_root_path = os.path.realpath(os.path.dirname(os.path.abspath(__file__)).replace('\\', '/')) extension_dirs = ( "wbs_kernel/", ) os.chdir(addon_root_path) os.makedirs("build", exist_ok=True) # clean up build data if previous build type does not match with current, or if set manually with an option if build_type_mismatch(debug) or clean: clean = True clean_build_data(extension_dirs) print_info('\nBuilding C++ extensions...') for module_relpath in extension_dirs: try: os.chdir(os.path.join(addon_root_path, module_relpath)) launch_args = [PYTHON_PATH, "setup.py", 'build_clib', 'build_ext', '--inplace'] if debug: launch_args.append('--wbs_debug') status = subprocess.call(launch_args) if status: print_error(f"\nProcess returned error code {status} while building module \"{module_relpath}\"") sys.exit(1) except PermissionError: print_error("\nThis build script may need to be called with admin (root) rights.") sys.exit(1) except RuntimeError: print_error("\nUnknown error occurred.") sys.exit(1) # build pywowlib os.chdir(addon_root_path) launch_args = [PYTHON_PATH, "pywowlib/build.py"] if debug: launch_args.append('--debug') if clean: launch_args.append('--clean') status = subprocess.call(launch_args) if status: print_error("\nError building pywowlib.") sys.exit(1) # install required Python modules if not no_req: print_info('\nInstalling third-party Python modules...') def install_requirements(f): for line in f.readlines(): status = subprocess.call([PYTHON_PATH, '-m', 'pip', 'install', line, '-t', 'third_party', '--upgrade']) if status: print('\nError: failed installing module \"{}\". See pip error above.'.format(line)) sys.exit(1) with open('requirements.txt') as f: install_requirements(f) with open('pywowlib/requirements.txt') as f: install_requirements(f) else: print_info("\nWarning: Third-party Python modules will not be installed. (--noreq option)") print_succes('\nWBS building finished successfully.', "\nTotal build time: ", time.strftime("%M minutes %S seconds\a", time.gmtime(time.time() - start_time))) if do_distribute: create_distribution(addon_root_path, dist_path) if __name__ == "__main__": parser = argparse.ArgumentParser(description="Build WoW Blender Studio." "\nMake sure your Python version matches the version of Python" " inside the target Blender distribution." "\n" "\nRequired dependencies are:" "\n All:" "\n * pip (https://pip.pypa.io/en/stable/installation/)" "\n * Cython (pip install Cython)" "\n * CMake (commandline CMake is required, see: https://cmake.org" "\n * C++ compiler (MSVC for Windows, GCC/Clang for Linux/Mac)" "\n" "\n Linux:" "\n * OpenGL (sudo apt install libglu1-mesa-dev freeglut3-dev)" "\n * Python headers (sudo apt install python3-dev)" , formatter_class=argparse.RawTextHelpFormatter) parser.add_argument('--dist', type=str, help='create a distribution of WBS in specified directory') parser.add_argument('--debug', action='store_true', help='compile WBS in debug mode') parser.add_argument('--clean', action='store_true', help='erase previous build files and recompile from scratch') parser.add_argument('--noreq', action='store_true', help='do not pull python modules from PyPi') args = parser.parse_args() build_project(args.debug, args.clean, args.noreq, args.dist)