import os import os.path from options import * class BuildError(Exception): '''Generic exception for custom build errors''' def __init__(self, msg): super(BuildError, self).__init__(msg) self.message = msg def run_command(command, args=[], cwd=None, env=None, name='command'): def cmd_args_to_str(cmd_args): return ' '.join([arg if not ' ' in arg else '"%s"' % arg for arg in cmd_args]) assert isinstance(command, str) and isinstance(args, list) args = [command] + args check_call_args = {} if cwd is not None: check_call_args['cwd'] = cwd if env is not None: check_call_args['env'] = env import subprocess try: print('Running command \'%s\': %s' % (name, subprocess.list2cmdline(args))) subprocess.check_call(args, **check_call_args) print('Command \'%s\' completed successfully' % name) except subprocess.CalledProcessError as e: raise BuildError('\'%s\' exited with error code: %s' % (name, e.returncode)) print_env_sh_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'print_env.sh') def source(script: str, cwd=None) -> dict: popen_args = {} if cwd is not None: popen_args['cwd'] = cwd import subprocess cmd = 'bash -c \'source %s; bash %s\'' % (script, print_env_sh_path) proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True, **popen_args) output = proc.communicate()[0] return dict(line.split('=', 1) for line in output.decode().split('\x00') if line) # Creates the directory if no other file or directory with the same path exists def mkdir_p(path): if not os.path.exists(path): print('creating directory: ' + path) os.makedirs(path) # Remove files and/or directories recursively def rm_rf(*paths): from shutil import rmtree for path in paths: if os.path.isfile(path): print('removing file: ' + path) os.remove(path) elif os.path.isdir(path): print('removing directory and its contents: ' + path) rmtree(path) ENV_PATH_SEP = ';' if os.name == 'nt' else ':' def find_executable(name) -> str: is_windows = os.name == 'nt' windows_exts = os.environ['PATHEXT'].split(ENV_PATH_SEP) if is_windows else None path_dirs = os.environ['PATH'].split(ENV_PATH_SEP) search_dirs = path_dirs + [os.getcwd()] # cwd is last in the list for dir in search_dirs: path = os.path.join(dir, name) if is_windows: for extension in windows_exts: path_with_ext = path + extension if os.path.isfile(path_with_ext) and os.access(path_with_ext, os.X_OK): return path_with_ext else: if os.path.isfile(path) and os.access(path, os.X_OK): return path return '' def replace_in_new_file(src_file, search, replace, dst_file): with open(src_file, 'r') as file: content = file.read() content = content.replace(search, replace) with open(dst_file, 'w') as file: file.write(content) def replace_in_file(filepath, search, replace): replace_in_new_file(src_file=filepath, search=search, replace=replace, dst_file=filepath) def touch(filepath: str): import pathlib pathlib.Path(filepath).touch() def get_emsdk_root(): # Shamelessly copied from Godot's detect.py em_config_file = os.getenv('EM_CONFIG') or os.path.expanduser('~/.emscripten') if not os.path.exists(em_config_file): raise BuildError("Emscripten configuration file '%s' does not exist" % em_config_file) with open(em_config_file) as f: em_config = {} try: # Emscripten configuration file is a Python file with simple assignments. exec(f.read(), em_config) except StandardError as e: raise BuildError("Emscripten configuration file '%s' is invalid:\n%s" % (em_config_file, e)) if 'BINARYEN_ROOT' in em_config and os.path.isdir(os.path.join(em_config.get('BINARYEN_ROOT'), 'emscripten')): # New style, emscripten path as a subfolder of BINARYEN_ROOT return os.path.join(em_config.get('BINARYEN_ROOT'), 'emscripten') elif 'EMSCRIPTEN_ROOT' in em_config: # Old style (but can be there as a result from previous activation, so do last) return em_config.get('EMSCRIPTEN_ROOT') else: raise BuildError("'BINARYEN_ROOT' or 'EMSCRIPTEN_ROOT' missing in Emscripten configuration file '%s'" % em_config_file) def globs(pathnames, dirpath='.'): import glob files = [] for pathname in pathnames: files.extend(glob.glob(os.path.join(dirpath, pathname))) return files def xcrun_find_sdk(sdk_name): import subprocess xcrun_output = subprocess.check_output(['xcrun', '--sdk', sdk_name, '--show-sdk-path']).decode().strip() if xcrun_output.startswith('xcrun: error: SDK "%s" cannot be located' % sdk_name): return '' sdk_path = xcrun_output return sdk_path def chmod_plus_x(file): import os import stat umask = os.umask(0) os.umask(umask) st = os.stat(file) os.chmod(file, st.st_mode | ((stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) & ~umask)) def get_clang_resource_dir(clang_command): import shlex from subprocess import check_output return check_output(shlex.split(clang_command) + ['-print-resource-dir']).strip().decode('utf-8') def try_find_libclang(toolchain_path: str = '', llvm_config=''): import sys from subprocess import check_output hint_paths = [] if toolchain_path: libclang = os.path.join(toolchain_path, 'usr', 'lib', 'libclang.dylib') if os.path.isfile(libclang): print('Found libclang at: \'%s\'' % libclang) return libclang if not llvm_config: llvm_config = find_executable('llvm-config') if not llvm_config: print('WARNING: llvm-config not found') return '' elif not os.path.isfile(llvm_config): raise RuntimeError('Specified llvm-config file not found: \'%s\'' % llvm_config) llvm_libdir = check_output([llvm_config, '--libdir']).strip().decode('utf-8') if llvm_libdir: libsuffix = '.dylib' if sys.platform == 'darwin' else '.so' hints = ['libclang', 'clang'] libclang = next((p for p in [os.path.join(llvm_libdir, h + libsuffix) for h in hints] if os.path.isfile(p)), '') if libclang: print('Found libclang at: \'%s\'' % libclang) return libclang return '' def create_osxcross_wrapper(opts: RuntimeOpts, product: str, target: str, toolchain_path : str): # OSXCROSS toolchain executables use rpath to locate the toolchain's shared libraries. # However, when moving the toolchain without care, the rpaths can be broken. # Since fixing the rpaths can be tedious, we use this wrapper to override LD_LIBRARY_PATH. # The reason we don't just run configure and make with LD_LIBRARY_PATH is because # we want the resulting configuration to be independent from out python scripts. wrapper_src = """#!/bin/bash OSXCROSS_COMMAND=$1; shift; export LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:%s"; ${OSXCROSS_COMMAND} "$@"; exit $?; """ % os.path.join(toolchain_path, 'lib') build_dir = os.path.join(opts.configure_dir, '%s-%s-%s' % (product, target, opts.configuration)) wrapper_path = os.path.join(build_dir, 'osxcross_cmd_wrapper.sh') mkdir_p(build_dir) with open(wrapper_path, 'w') as f: f.write(wrapper_src) chmod_plus_x(wrapper_path) return wrapper_path