#!/usr/bin/env python3 """ Runs an application in a manner that allows it to discover various engine resources that may be in unexpected locations. In particular: this allows running an application 'in-tree' under a debugger with dependencies and resources out of line. The base assumption is that this is probably a special circumstance and hence should be run reasonably verbose but not interfere with a probable debugger session. """ import platform import os import subprocess import sys # We need these ASAN options set so that nVidia's driver doesn't force an # immediate halt. # # invalid_pointer_pairs gives overly verbose reports from deep within some # core dependencies so it's not enabled by default. asan_options = { 'protect_shadow_gap': 0, 'detect_stack_use_after_return': 1, #'detect_invalid_pointer_pairs': 1, } # A path that contains our application, the resources, and the runtime # configuration. # # It is probably assumed that the root will directly contain a 'config.json'; # but there's a definitely an assumption that we won't travel deeper into a # hierarchy without foreknowledge that we're dealing with a good path, so don't # set this to something stupid like '/'. # # By default we find the first path below us that contains a 'config.json' # file under the assumption this script sits within a game directory structure. def find_root(init, default): cursor = init while True: if os.path.isfile(os.path.join(cursor, 'config.json')): return cursor parent = os.path.dirname(cursor) if parent == cursor: return default cursor = parent root = os.path.abspath(os.path.dirname(__file__)) root = find_root(root, default=root) defaults = { # don't break terrifically often, given we're probably running a debugger, # but write a lot of information to the console. 'BREAK_LEVEL': 'CRITICAL', 'LOG_LEVEL': 'DEBUG', 'CL_VENDOR_IGNORE': 'intel64.icd', 'ROOT': root, 'ASAN_OPTIONS': ':'.join (f"{k}={v}" for k,v in asan_options.items()) } # Overwrite our defaults with the current environment. This ensures a user can # override any of the above variables easily from the commandline. env = defaults.copy() env.update(os.environ) # MinGW pretends to be its own platform. Try to work around their deception by # testing if the platform string looks like something that's actually useful. def is_really_windows() -> bool: name = platform.system() if name == 'Windows': return True if name.startswith('MINGW'): return True return False # MSYS2 wants to fuck us over by supplying a CMake that gives something # approximating native paths, and by _also_ providing an environment that # cannot use these paths in places like PATH. # # Break the paths to conform to their expectations if we're under Windows. def break_path_for_msys2(path): if not is_really_windows(): return path # Test if we have a path of the form "C:/foo" (as opposed to a # relative path, or an MSYS2 path like "/c/foo") if path[1] != ':': return path return '/' + path[0] + path[2:] # Windows has its own unique rules for library lookups. We record the # environment variable which effects path lookups and the separator and the # target directory for Windows and for every single other system we're likely # to ever deal with... if is_really_windows(): separator = ':' depsdir = ['bin'] searchvar = 'PATH' else: separator = ':' depsdir = ['lib', 'lib64'] searchvar = 'LD_LIBRARY_PATH' # append the in-tree dependencies to the library path search = separator.join( break_path_for_msys2("@CMAKE_CURRENT_BINARY_DIR@/deps/") + i for i in depsdir ) if searchvar in env: env[searchvar] = search + separator + env[searchvar] else: env[searchvar] = search # It's probably unnecessary to pipe std res = subprocess.run(sys.argv[1:], env=env, stderr=sys.stderr, stdout=sys.stdout) sys.exit(res.returncode)