From 368b39044cf3111b56094756c73410424205d68a Mon Sep 17 00:00:00 2001 From: Eric Date: Sat, 3 Jun 2023 11:25:55 -0700 Subject: [PATCH] tidying --- .efrocachemap | 32 +++---- tools/batools/appmodule.py | 140 ++++++++++++++++++++++++++++++ tools/batools/featureset.py | 4 +- tools/batools/pcommand2.py | 104 +--------------------- tools/batools/project/_updater.py | 113 +----------------------- tools/batools/spinoff/__init__.py | 2 + tools/batools/spinoff/_context.py | 22 ++++- tools/batools/spinoff/_test.py | 118 +++++++++++++++++++++++++ tools/efrotools/__init__.py | 1 - 9 files changed, 304 insertions(+), 232 deletions(-) create mode 100755 tools/batools/appmodule.py create mode 100644 tools/batools/spinoff/_test.py diff --git a/.efrocachemap b/.efrocachemap index a68440a1..fcc6abb7 100644 --- a/.efrocachemap +++ b/.efrocachemap @@ -4080,18 +4080,18 @@ "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/fb/46/34eada1e570cce2107cb55a033e8", "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/1d/3e/be779b3740cda2a7d98418d4007a", "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/18/b7/4389f6000decbcde1044180b134a", - "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/4a/91/2c3bf6cca0baecc16138db7fe7ff", + "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/a0/35/48a61b9ea9369bbe8e6bbb7a2d81", "build/prefab/full/mac_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/01/b4/31635bc46cbda94cc73fc019d28c", - "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/13/3d/50286e4d2fe1fc7507ba20b45ca2", + "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/8a/d9/0364942cd8fc9cebbb8b2e355455", "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/d7/17/7f186bd856ea7bd18b2fc8d64639", - "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/b9/63/640aa2e767450800ff481c51b665", + "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/fe/b8/9865abef73b150348a113fd2bcf0", "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/19/ca/ae8153fb5fd0ea1045d386c31134", - "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/70/e8/796d18c51690f54efcb3df22c048", + "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/cb/54/ff08149cf2d0c2430e964ad0b9f3", "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/e2/8a/0ae529b92bcf4afe5d1c9c797e96", - "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/a3/d7/f415b115cd0e348be8aa75e28101", - "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/58/77/7244f67e35fcb0ab4047d5cc6f5e", - "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/91/de/bef5f0a5b9a0c6d50f4e2922a959", - "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/75/a5/42ad0f7c2944b1033e5b12395743", + "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/15/c2/f85086c32e40be84f46f26d90790", + "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/70/e5/266a0a37f2c7566624fdbc08f1bd", + "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/29/b2/723a9c0ac8c501cf0d313c7c98b0", + "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/ed/9a/a2611b9814196d2def42e363df94", "build/prefab/lib/linux_arm64_gui/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/d4/6a/dd303a200b98a56ba3b100277057", "build/prefab/lib/linux_arm64_gui/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/fc/2c/2996c558fb408a548fdd37398c9a", "build/prefab/lib/linux_arm64_server/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/ed/28/b7a72be7ae1bd2b58dda4b6902a0", @@ -4108,14 +4108,14 @@ "build/prefab/lib/mac_x86_64_gui/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/71/f6/691482915ad58ea1e953cc23d74c", "build/prefab/lib/mac_x86_64_server/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/b8/2b/6ec8c78980a62e3e0ee4b36ece04", "build/prefab/lib/mac_x86_64_server/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/4e/56/a95c987b2a371759896b037fea86", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "https://files.ballistica.net/cache/ba1/60/31/7c82d61f768515a6f5f8f20a967b", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "https://files.ballistica.net/cache/ba1/ba/38/d98a316972f47a5509be6ab28a96", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "https://files.ballistica.net/cache/ba1/a5/8d/8120340f79d9f9244013ef24c9da", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "https://files.ballistica.net/cache/ba1/84/47/90ccac6eb182c16a973855a160ee", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "https://files.ballistica.net/cache/ba1/5f/7b/ea3d74c27c2272084b459814986f", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "https://files.ballistica.net/cache/ba1/f6/a9/a780cd9b23e680a9f5cc87e5ac21", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "https://files.ballistica.net/cache/ba1/8c/9e/3902170e5e8662ec1fccc720ed48", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "https://files.ballistica.net/cache/ba1/c9/d4/5fa5dc68d04b906ef22a10eae5ae", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "https://files.ballistica.net/cache/ba1/a7/b0/aae9f0f77bc4cf5b7367554e024f", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "https://files.ballistica.net/cache/ba1/81/6e/1cec831611669051695029bd1eeb", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "https://files.ballistica.net/cache/ba1/43/4e/0050d1d6d47cf785a498d9060d30", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "https://files.ballistica.net/cache/ba1/82/fb/c202aa3d4857dfa846afe049ed79", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "https://files.ballistica.net/cache/ba1/cd/27/e34e5ddcf8f5198adf6d896492c8", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "https://files.ballistica.net/cache/ba1/8e/e9/e4af2706ec10fb842743e9227b6a", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "https://files.ballistica.net/cache/ba1/37/b3/75a6d096d1b0801badc116e6b122", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "https://files.ballistica.net/cache/ba1/3b/c1/32817f574106264dfd67563d9ce1", "src/assets/ba_data/python/babase/_mgen/__init__.py": "https://files.ballistica.net/cache/ba1/52/c6/c11130af7b10d6c0321add5518fa", "src/assets/ba_data/python/babase/_mgen/enums.py": "https://files.ballistica.net/cache/ba1/38/c3/1dedd5e74f2508efc5974c8815a1", "src/ballistica/base/mgen/pyembed/binding_base.inc": "https://files.ballistica.net/cache/ba1/d5/4a/0e480a855ce83709bd7f6761107d", diff --git a/tools/batools/appmodule.py b/tools/batools/appmodule.py new file mode 100755 index 00000000..f8fd1551 --- /dev/null +++ b/tools/batools/appmodule.py @@ -0,0 +1,140 @@ +# Released under the MIT License. See LICENSE for details. +# +"""Generates parts of babase._app.py. + +This includes things like subsystem attributes for all feature-sets that +define them. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from efro.error import CleanError + +if TYPE_CHECKING: + from batools.featureset import FeatureSet + + +def generate_app_module( + feature_sets: dict[str, FeatureSet], existing_data: str +) -> str: + """Generate babase._app.py based on its existing version.""" + + # pylint: disable=too-many-locals + import textwrap + + from efrotools import replace_section + + out = '' + + fsets = feature_sets + + out = existing_data + + info = '# This section autogenerated by project-update.' + indent = ' ' + + # Import modules we need for feature-set subsystems. + contents = '' + for _fsname, fset in sorted(fsets.items()): + if fset.has_python_app_subsystem: + modname = fset.name_python_package + classname = f'{fset.name_camel}Subsystem' + contents += f'from {modname} import {classname}\n' + out = replace_section( + out, + f'{indent}# __FEATURESET_APP_SUBSYSTEM_IMPORTS_BEGIN__\n', + f'{indent}# __FEATURESET_APP_SUBSYSTEM_IMPORTS_END__\n', + textwrap.indent( + f'{info}\n\n{contents}\n' if contents else f'{info}\n', indent + ), + keep_markers=True, + ) + + # Calc which feature-sets are soft-required by any of the ones here + # but not included here. For those we'll expose app-subsystems that + # always return None. + missing_soft_fset_names = set[str]() + + for fset in fsets.values(): + for softreq in fset.soft_requirements: + if softreq not in fsets: + missing_soft_fset_names.add(softreq) + + all_fset_names = missing_soft_fset_names | fsets.keys() + + # Add properties to instantiate feature-set subsystems. + contents = '' + + for fsetname in sorted(all_fset_names): + # for _fsname, fset in sorted(fsets.items()): + if fsetname in missing_soft_fset_names: + contents += ( + f'\n' + f'@cached_property\n' + f'def {fsetname}(self) -> Any | None:\n' + f' """Our {fsetname} subsystem (not available' + f' in this project)."""\n' + f'\n' + f' return None\n' + ) + else: + fset = fsets[fsetname] + if fset.has_python_app_subsystem: + if not fset.allow_as_soft_requirement: + raise CleanError( + f'allow_as_soft_requirement is False for' + f' feature-set {fset.name};' + f' this is not yet supported.' + ) + modname = fset.name_python_package + classname = f'{fset.name_camel}Subsystem' + contents += ( + f'\n' + f'@cached_property\n' + f'def {fset.name}(self) -> {classname} | None:\n' + f' """Our {fset.name} subsystem (if available)."""\n' + f'\n' + f' try:\n' + f' from {modname} import {classname}\n' + f'\n' + f' return {classname}()\n' + f' except ImportError:\n' + f' return None\n' + f' except Exception:\n' + f" logging.exception('Error importing" + f" {modname}.')\n" + f' return None\n' + ) + out = replace_section( + out, + f'{indent}# __FEATURESET_APP_SUBSYSTEM_PROPERTIES_BEGIN__\n', + f'{indent}# __FEATURESET_APP_SUBSYSTEM_PROPERTIES_END__\n', + textwrap.indent(f'{info}\n{contents}\n', indent), + keep_markers=True, + ) + + # Set default app-mode-selection logic. + + # TEMP - fill this out with proper logic/options. + if 'scene_v1' in fsets: + contents = 'import bascenev1\n\nreturn bascenev1.SceneV1AppMode\n' + else: + contents = "raise RuntimeError('FIXME: unimplemented.')\n" + + indent = ' ' + out = replace_section( + out, + f'{indent}# __DEFAULT_APP_MODE_SELECTION_BEGIN__\n', + f'{indent}# __DEFAULT_APP_MODE_SELECTION_END__\n', + textwrap.indent(f'{info}\n\n{contents}\n', indent), + keep_markers=True, + ) + + # Note: we *should* format this string, but because this code + # runs with every project update I'm just gonna try to keep the + # formatting correct manually for now to save a bit of time. + # (project update time jumps from 0.3 to 0.5 seconds if I format + # thie one file). + return out diff --git a/tools/batools/featureset.py b/tools/batools/featureset.py index e63d4afe..3db11b97 100644 --- a/tools/batools/featureset.py +++ b/tools/batools/featureset.py @@ -51,8 +51,8 @@ class FeatureSet: # Whether this featureset defines a native Python module within # its C++ code. The build process will try to create dummy - # modules for all native modules you must tell it if you don't - # have one. + # modules for all native modules, so to avoid errors you must + # tell it if you don't have one. self.has_native_python_module = True # If True, for feature-set 'foo_bar', the build system will diff --git a/tools/batools/pcommand2.py b/tools/batools/pcommand2.py index 090c489d..f6e4ab2a 100644 --- a/tools/batools/pcommand2.py +++ b/tools/batools/pcommand2.py @@ -306,109 +306,9 @@ def android_archive_unstripped_libs() -> None: def spinoff_test() -> None: """Test spinoff functionality.""" - import os - import subprocess + import batools.spinoff - from efrotools import extract_flag - from efro.terminal import Clr - from efro.error import CleanError - - args = sys.argv[2:] - - submodule_parent = extract_flag(args, '--submodule-parent') - - # A spinoff symlink means we're a spun-off project. - if os.path.islink('tools/spinoff'): - raise CleanError( - 'This must be run in a src project; this appears to be a dst.' - ) - if len(args) != 1: - raise CleanError('Expected 1 arg.') - testtype = args[0] - if testtype in {'empty', 'base'}: - path = f'build/spinofftest/{testtype}' - print( - f'{Clr.SBLU}{Clr.BLD}Running spinoff test' - f" {Clr.RST}{Clr.BLD}'{testtype}'{Clr.RST}{Clr.SBLU}{Clr.BLD}" - f'...{Clr.RST}', - flush=True, - ) - - if os.path.exists(path): - if bool(False): - subprocess.run(['rm', '-rf', path], check=True) - submpath = os.path.join(path, 'submodules/ballistica') - if os.path.exists(submpath): - print( - f'{Clr.BLU}Pulling latest parent submodule' - f' for existing test setup...{Clr.RST}', - flush=True, - ) - subprocess.run( - f'cd "{submpath}" && git checkout master && git pull', - shell=True, - check=True, - ) - - else: - cmd = [ - './tools/spinoff', - 'create', - 'SpinoffTest', - path, - '--featuresets', - 'none' if testtype == 'empty' else testtype, - ] + (['--submodule-parent'] if submodule_parent else []) - - # Show the spinoff command we'd use here. - print(Clr.MAG + ' '.join(cmd) + Clr.RST, flush=True) - - # Avoid the 'what to do next' help. - subprocess.run( - cmd + ['--noninteractive'], - check=True, - ) - - print(f'{Clr.MAG}tools/spinoff update{Clr.RST}', flush=True) - subprocess.run(['tools/spinoff', 'update'], cwd=path, check=True) - # subprocess.run(['make', 'cmake-server-binary'], cwd=path, check=True) - - # Now let's simply run the mypy target. This will compile a - # binary, use that binary to generate dummy Python modules, and - # then check the assembled set of Python scripts. If all that - # goes through it tells us that this spinoff project is at least - # basically functional. - subprocess.run( - ['make', 'mypy'], - cwd=path, - env=dict( - os.environ, - BA_ENABLE_DUMMY_MODULE_BINARY_BUILDS='1', - BA_DUMMY_MODULE_BINARY_BUILDS_USE_HEADLESS='1', - ), - check=True, - ) - - # Run the binary with a --help arg and make sure it spits - # out what we expect it to. - # DISABLING: the dummy-module generation part of the mypy target - # covers this. - if bool(False): - help_output = subprocess.run( - [ - 'build/cmake/server-debug/dist/spinofftest_headless', - '--help', - ], - cwd=path, - check=True, - capture_output=True, - ).stdout.decode() - if '-h, --help ' not in help_output: - raise RuntimeError( - 'Unexpected output when running test command.' - ) - else: - raise CleanError(f"Invalid test type '{testtype}'.") + batools.spinoff.spinoff_test(sys.argv[2:]) def spinoff_check_submodule_parent() -> None: diff --git a/tools/batools/project/_updater.py b/tools/batools/project/_updater.py index 3a756b2b..d1bedf9f 100755 --- a/tools/batools/project/_updater.py +++ b/tools/batools/project/_updater.py @@ -696,119 +696,12 @@ class ProjectUpdater: ) def _generate_app_module(self, path: str, existing_data: str) -> None: - # pylint: disable=too-many-locals - import textwrap + from batools.appmodule import generate_app_module - from efrotools import replace_section - - fsets = self.feature_sets - - out = existing_data - - info = '# This section autogenerated by project-update.' - indent = ' ' - - # Import modules we need for feature-set subsystems. - contents = '' - for _fsname, fset in sorted(fsets.items()): - if fset.has_python_app_subsystem: - modname = fset.name_python_package - classname = f'{fset.name_camel}Subsystem' - contents += f'from {modname} import {classname}\n' - out = replace_section( - out, - f'{indent}# __FEATURESET_APP_SUBSYSTEM_IMPORTS_BEGIN__\n', - f'{indent}# __FEATURESET_APP_SUBSYSTEM_IMPORTS_END__\n', - textwrap.indent(f'{info}\n\n{contents}\n', indent), - keep_markers=True, + self._generated_files[path] = generate_app_module( + self.feature_sets, existing_data ) - # Calc which feature-sets are soft-required by any of the ones here - # but not included here. For those we'll expose app-subsystems that - # always return None. - missing_soft_fset_names = set[str]() - - for fset in fsets.values(): - for softreq in fset.soft_requirements: - if softreq not in fsets: - missing_soft_fset_names.add(softreq) - - all_fset_names = missing_soft_fset_names | fsets.keys() - - # Add properties to instantiate feature-set subsystems. - contents = '' - - for fsetname in sorted(all_fset_names): - # for _fsname, fset in sorted(fsets.items()): - if fsetname in missing_soft_fset_names: - contents += ( - f'\n' - f'@cached_property\n' - f'def {fsetname}(self) -> Any | None:\n' - f' """Our {fsetname} subsystem (not available)."""\n' - f'\n' - f' return None\n' - ) - else: - fset = fsets[fsetname] - if fset.has_python_app_subsystem: - if not fset.allow_as_soft_requirement: - raise CleanError( - f'allow_as_soft_requirement is False for' - f' feature-set {fset.name};' - f' this is not yet supported.' - ) - modname = fset.name_python_package - classname = f'{fset.name_camel}Subsystem' - contents += ( - f'\n' - f'@cached_property\n' - f'def {fset.name}(self) -> {classname} | None:\n' - f' """Our {fset.name} subsystem (if available)."""\n' - f'\n' - f' try:\n' - f' from {modname} import {classname}\n' - f'\n' - f' return {classname}()\n' - f' except ImportError:\n' - f' return None\n' - f' except Exception:\n' - f" logging.exception('Error importing" - f" {modname}.')\n" - f' return None\n' - ) - out = replace_section( - out, - f'{indent}# __FEATURESET_APP_SUBSYSTEM_PROPERTIES_BEGIN__\n', - f'{indent}# __FEATURESET_APP_SUBSYSTEM_PROPERTIES_END__\n', - textwrap.indent(f'{info}\n{contents}\n', indent), - keep_markers=True, - ) - - # Set default app-mode-selection logic. - - # TEMP - fill this out with proper logic/options. - if 'scene_v1' in fsets: - contents = 'import bascenev1\n\nreturn bascenev1.SceneV1AppMode\n' - else: - contents = "raise RuntimeError('FIXME: unimplemented.')\n" - - indent = ' ' - out = replace_section( - out, - f'{indent}# __DEFAULT_APP_MODE_SELECTION_BEGIN__\n', - f'{indent}# __DEFAULT_APP_MODE_SELECTION_END__\n', - textwrap.indent(f'{info}\n\n{contents}\n', indent), - keep_markers=True, - ) - - # Note: we *should* format this string, but because this code - # runs with every project update I'm just gonna try to keep the - # formatting correct manually for now to save a bit of time. - # (project update time jumps from 0.3 to 0.5 seconds if I format - # thie one file). - self._generated_files[path] = out - def _update_meta_makefile(self) -> None: self.enqueue_update('src/meta/Makefile') diff --git a/tools/batools/spinoff/__init__.py b/tools/batools/spinoff/__init__.py index f54e5402..eda560bb 100644 --- a/tools/batools/spinoff/__init__.py +++ b/tools/batools/spinoff/__init__.py @@ -16,8 +16,10 @@ submodule and remove the 'spinoff' section in .gitignore. from batools.spinoff._context import SpinoffContext from batools.spinoff._main import spinoff_main +from batools.spinoff._test import spinoff_test __all__ = [ 'SpinoffContext', 'spinoff_main', + 'spinoff_test', ] diff --git a/tools/batools/spinoff/_context.py b/tools/batools/spinoff/_context.py index d21880d1..eb6bc209 100644 --- a/tools/batools/spinoff/_context.py +++ b/tools/batools/spinoff/_context.py @@ -341,12 +341,32 @@ class SpinoffContext: self._read_state() + # First, ask git if there are any untracked files in src. we use + # git's managed file list so these wouldn't get synced which + # would be confusing. So we'd rather just error in this case. + try: + output = subprocess.check_output( + ['git', 'status', '--porcelain=v2'], + cwd=self._src_root, + ).decode() + if any(line.startswith('?') for line in output.splitlines()): + raise CleanError( + 'There appear to be files in the src project' + ' untracked by git. Everything must be added to' + ' git for spinoff to function.' + ) + except subprocess.CalledProcessError as exc: + raise CleanError( + "'git status' command failed in src dir." + ' Spinoff requires the src project to be git managed.' + ) from exc + # Get the list of src files managed by git. self._src_git_files = set[str]( subprocess.run( ['git', 'ls-files'], check=True, - cwd=os.path.join(self._src_root), + cwd=self._src_root, capture_output=True, ) .stdout.decode() diff --git a/tools/batools/spinoff/_test.py b/tools/batools/spinoff/_test.py new file mode 100644 index 00000000..c6e7fa8b --- /dev/null +++ b/tools/batools/spinoff/_test.py @@ -0,0 +1,118 @@ +# Released under the MIT License. See LICENSE for details. +# +"""Tests for spinoff.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + pass + + +def spinoff_test(args: list[str]) -> None: + """High level test run command; accepts args and raises CleanErrors.""" + + import os + import subprocess + + from efrotools import extract_flag + from efro.terminal import Clr + from efro.error import CleanError + + submodule_parent = extract_flag(args, '--submodule-parent') + + # A spinoff symlink means we're a spun-off project. + if os.path.islink('tools/spinoff'): + raise CleanError( + 'This must be run in a src project; this appears to be a dst.' + ) + if len(args) != 1: + raise CleanError('Expected 1 arg.') + testtype = args[0] + if testtype in {'empty', 'base'}: + path = f'build/spinofftest/{testtype}' + print( + f'{Clr.BLD}Running spinoff test{Clr.RST}' + f" {Clr.SBLU}{Clr.BLD}'{testtype}'{Clr.RST}" + f' {Clr.BLD}in{Clr.RST}' + f" {Clr.BLU}'{path}'{Clr.RST}" + f'{Clr.BLD}...{Clr.RST}', + flush=True, + ) + + if os.path.exists(path): + if bool(False): + subprocess.run(['rm', '-rf', path], check=True) + submpath = os.path.join(path, 'submodules/ballistica') + if os.path.exists(submpath): + print( + f'{Clr.BLU}Pulling latest parent submodule' + f' for existing test setup...{Clr.RST}', + flush=True, + ) + subprocess.run( + f'cd "{submpath}" && git checkout master && git pull', + shell=True, + check=True, + ) + + else: + cmd = [ + './tools/spinoff', + 'create', + 'SpinoffTest', + path, + '--featuresets', + 'none' if testtype == 'empty' else testtype, + ] + (['--submodule-parent'] if submodule_parent else []) + + # Show the spinoff command we'd use here. + print(Clr.MAG + ' '.join(cmd) + Clr.RST, flush=True) + + # Avoid the 'what to do next' help. + subprocess.run( + cmd + ['--noninteractive'], + check=True, + ) + + print(f'{Clr.MAG}tools/spinoff update{Clr.RST}', flush=True) + subprocess.run(['tools/spinoff', 'update'], cwd=path, check=True) + # subprocess.run(['make', 'cmake-server-binary'], cwd=path, check=True) + + # Now let's simply run the mypy target. This will compile a + # binary, use that binary to generate dummy Python modules, and + # then check the assembled set of Python scripts. If all that + # goes through it tells us that this spinoff project is at least + # basically functional. + subprocess.run( + ['make', 'mypy'], + cwd=path, + env=dict( + os.environ, + BA_ENABLE_DUMMY_MODULE_BINARY_BUILDS='1', + BA_DUMMY_MODULE_BINARY_BUILDS_USE_HEADLESS='1', + ), + check=True, + ) + + # Run the binary with a --help arg and make sure it spits + # out what we expect it to. + # DISABLING: the dummy-module generation part of the mypy target + # covers this. + if bool(False): + help_output = subprocess.run( + [ + 'build/cmake/server-debug/dist/spinofftest_headless', + '--help', + ], + cwd=path, + check=True, + capture_output=True, + ).stdout.decode() + if '-h, --help ' not in help_output: + raise RuntimeError( + 'Unexpected output when running test command.' + ) + else: + raise CleanError(f"Invalid test type '{testtype}'.") diff --git a/tools/efrotools/__init__.py b/tools/efrotools/__init__.py index 28462e0a..b6630b8c 100644 --- a/tools/efrotools/__init__.py +++ b/tools/efrotools/__init__.py @@ -130,7 +130,6 @@ def extract_arg( return val -# FIXME: this has not been tested yet. def replace_section( text: str, begin_marker: str,