diff --git a/.efrocachemap b/.efrocachemap index 2cdd903b..e14a92b7 100644 --- a/.efrocachemap +++ b/.efrocachemap @@ -4072,26 +4072,26 @@ "build/assets/workspace/ninjafightplug.py": "https://files.ballistica.net/cache/ba1/c5/09/4f10b8a21ba87aa5509cff7a164b", "build/assets/workspace/onslaughtplug.py": "https://files.ballistica.net/cache/ba1/ff/0a/a354984f9c074dab0676ac7e4877", "build/assets/workspace/runaroundplug.py": "https://files.ballistica.net/cache/ba1/2a/1c/9ee5db6d1bceca7fa6638fb8abde", - "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/0e/83/a30a9b21d0256eb7252a3aea77cb", - "build/prefab/full/linux_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/82/17/0ea5d85ff01573e2de91821dd8c9", - "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/46/12/88dfbcad582747150b6efe8fefe7", - "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/62/1f/78a78ed612d81a2708d9948c72ee", - "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/2c/2d/5c062b903d94b492d676e457ba81", - "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/f3/15/b168df3090bcc2f1f156029934eb", - "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/6b/51/a39e9cf765dc9ec4052df4a6b33d", - "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/82/09/f2d830e10ae0c9a77071ba4a7008", - "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/eb/80/ee6315adac4e758f1802683c24e9", - "build/prefab/full/mac_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/48/be/fbfd2e4eee8993b06bcd6a1562fa", - "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/a9/7d/bad58995ca27599d4000c9507ea1", - "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/65/08/d274f08d7fb52896967cecc4af62", - "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/3b/fa/db2bc0b2d4324a969e91add25bd1", - "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/1b/42/fc1392fc5831d0e5717248608d04", - "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/cb/03/337ffd349762467008307c2afc99", - "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/37/62/81f812c8e28ea74ab8f9d0b1c7e4", - "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/fb/96/05c6846f69abf6c69ed3a756d66f", - "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/ff/79/6a67ceb295488a2659d57778cfd1", - "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/e1/e4/4e3dd2af6155d2f647a101551932", - "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/e9/32/78bec9589f2271a5d70c48048815", + "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/01/62/032e7f17e55eb4d1a46d00b7f8b3", + "build/prefab/full/linux_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/d9/db/295a7e947eab3626dd09344f8773", + "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/4f/23/abf9b8a3d8382e132001a07ce659", + "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/8b/e6/969ff181328b319b0d0d7ba737b5", + "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/3f/71/ee0b615ae5737b07b14611fba521", + "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/ed/7b/f6b7d63868eff24231ddb4f08acc", + "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/b0/ed/b7c1d78cf2f4ca9501ae6ada5f2a", + "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/18/5b/eaea207321a10f13c932a3fd66d3", + "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/b6/f9/773f268c4ebd8cf0d663cc9466a5", + "build/prefab/full/mac_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/a8/5c/40fb49f7a0e1a65496766c5767e6", + "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/e2/c8/59625490bbbb456617eebb9c4ef3", + "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/6e/79/66ccaa09094203be7f1bd96ba81b", + "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/f0/94/1fd51fa09da1fc2f67cfc530dccf", + "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/84/af/9a03f2688c69a9f704cb8c4c52df", + "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/d9/ac/6320a984d2b5d9c7c6dffdf596b8", + "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/b2/0f/acd0b9a413537b781d21e9d3d48f", + "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/08/3d/8b85009c5e8c8d628c4c65aa8f59", + "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/e9/95/7621c5397b87d504240bbb28bd05", + "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/89/fc/bf7301a9c8b6e0bb310fd9c84b36", + "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/7e/34/3664bb2e4760264eafa82a240b4c", "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,17 +4108,17 @@ "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/32/a0/8bf135cfa577c08c1e223fcf2031", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "https://files.ballistica.net/cache/ba1/33/a6/428f8a5f54aa10495da308af1e33", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "https://files.ballistica.net/cache/ba1/ae/0a/020e5df843adc6c48c5d1531eac4", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "https://files.ballistica.net/cache/ba1/59/83/ba94efb820dbca8ea42944c045ab", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "https://files.ballistica.net/cache/ba1/c6/de/4100a3f3b8d69fea601fe2810d18", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "https://files.ballistica.net/cache/ba1/ed/9f/c3ebb7d1e0032da37d8f0a0e88ad", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "https://files.ballistica.net/cache/ba1/d4/72/128d744f26cdb804c08f8241a89a", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "https://files.ballistica.net/cache/ba1/1b/57/ba539fbf787b1cde81d12bcdfb37", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "https://files.ballistica.net/cache/ba1/46/b7/2b333df846670ad9b19c118dcd0f", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "https://files.ballistica.net/cache/ba1/3f/09/6d61b76e32baea0794483d3a8ad3", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "https://files.ballistica.net/cache/ba1/9e/0d/0407b97c24f4cf32e7c9aeba1842", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "https://files.ballistica.net/cache/ba1/12/ea/36c5d4aee512942eb9bb0866bf81", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "https://files.ballistica.net/cache/ba1/05/57/f9451c7bc39454a27afee023ce54", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "https://files.ballistica.net/cache/ba1/3c/38/7ab498b9f25e73062454c3b1d4af", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "https://files.ballistica.net/cache/ba1/56/8b/376666f0a064ebe26a0f5f61beb9", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "https://files.ballistica.net/cache/ba1/c9/f5/08f11ae1a959b9052c7fd799487e", "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/b4/3d/e352190a0e5673d101c0f3ee3ad2", + "src/ballistica/base/mgen/pyembed/binding_base.inc": "https://files.ballistica.net/cache/ba1/d5/4a/0e480a855ce83709bd7f6761107d", "src/ballistica/classic/mgen/pyembed/binding_classic.inc": "https://files.ballistica.net/cache/ba1/b2/fd/f5c362e9093b2d0721fb59fc0056", "src/ballistica/core/mgen/pyembed/binding_core.inc": "https://files.ballistica.net/cache/ba1/71/b8/c020cbb7f9a7ce3f347ccadd3df1", "src/ballistica/core/mgen/pyembed/env.inc": "https://files.ballistica.net/cache/ba1/7c/74/9b014c32c937757bf3ed36a4f848", diff --git a/CHANGELOG.md b/CHANGELOG.md index 313bccd9..196d71bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -### 1.7.20 (build 21030, api 8, 2023-05-31) +### 1.7.20 (build 21032, api 8, 2023-06-01) - This seems like a good time for a `refactoring` release in anticipation of changes coming in 1.8. Basically this means that a lot of things will be diff --git a/config/featuresets/README.md b/config/featuresets/README.md index c1412cd0..d9f1d579 100644 --- a/config/featuresets/README.md +++ b/config/featuresets/README.md @@ -20,13 +20,16 @@ with specific naming conventions: - **Feature Set Definition**: To define feature set `foo_bar`, a file must exist in this directory called `featureset_foo_bar.py`. - **Python Package**: If feature set `foo_bar` provides a Python package, it - should be a directory named `bafoobar` ('ba' prefix, spaces removed) + should be a directory named `bafoobar` ('ba' prefix, name with spaces removed) that lives under [Python source files](../../src/assets/ba_data/python). - **Native Code**: If feature set `foo_bar` provides a native component (C++ code or otherwise) it should live in a directory named `foo_bar` (unmodified feature set name) under [native source files](../../src/ballistica). - **Meta Package**: If feature set `foo_bar` provides a meta package (that is, code or data used to generate other source code), it should be a directory - named `bafoobarmeta` ('ba' prefix, 'meta' suffix, spaces removed) + named `bafoobarmeta` ('ba' prefix, name with spaces removed, 'meta' suffix) that lives under [meta source files](../../src/meta). +- **Test Package**: If feature set `foo_bar` provides a set of tests, it should + be a directory named `test_bafoobar` ('test_ba' prefix, name with spaces + removed) under [tests](../../tests). diff --git a/config/featuresets/featureset_base.py b/config/featuresets/featureset_base.py index 12b293fe..ed40bf24 100644 --- a/config/featuresets/featureset_base.py +++ b/config/featuresets/featureset_base.py @@ -4,8 +4,8 @@ from __future__ import annotations # This file is exec'ed by the spinoff system, allowing us to define -# values and behavior for this feature-set here in a way that can be -# type-checked alongside other project Python code. +# values and behavior for this feature-set here in a programmatic way +# that can also be type-checked alongside other project Python code. from batools.featureset import FeatureSet diff --git a/config/featuresets/featureset_classic.py b/config/featuresets/featureset_classic.py index 280994ea..26e0472d 100644 --- a/config/featuresets/featureset_classic.py +++ b/config/featuresets/featureset_classic.py @@ -4,8 +4,8 @@ from __future__ import annotations # This file is exec'ed by the spinoff system, allowing us to define -# values and behavior for this feature-set here in a way that can be -# type-checked alongside other project Python code. +# values and behavior for this feature-set here in a programmatic way +# that can also be type-checked alongside other project Python code. from batools.featureset import FeatureSet diff --git a/config/featuresets/featureset_core.py b/config/featuresets/featureset_core.py index 47f31147..9791b86d 100644 --- a/config/featuresets/featureset_core.py +++ b/config/featuresets/featureset_core.py @@ -4,8 +4,8 @@ from __future__ import annotations # This file is exec'ed by the spinoff system, allowing us to define -# values and behavior for this feature-set here in a way that can be -# type-checked alongside other project Python code. +# values and behavior for this feature-set here in a programmatic way +# that can also be type-checked alongside other project Python code. from batools.featureset import FeatureSet diff --git a/config/featuresets/featureset_plus.py b/config/featuresets/featureset_plus.py index 5fad68ad..45d78cef 100644 --- a/config/featuresets/featureset_plus.py +++ b/config/featuresets/featureset_plus.py @@ -4,8 +4,8 @@ from __future__ import annotations # This file is exec'ed by the spinoff system, allowing us to define -# values and behavior for this feature-set here in a way that can be -# type-checked alongside other project Python code. +# values and behavior for this feature-set here in a programmatic way +# that can also be type-checked alongside other project Python code. from batools.featureset import FeatureSet diff --git a/config/featuresets/featureset_scene_v1.py b/config/featuresets/featureset_scene_v1.py index 9773718f..843df767 100644 --- a/config/featuresets/featureset_scene_v1.py +++ b/config/featuresets/featureset_scene_v1.py @@ -4,8 +4,8 @@ from __future__ import annotations # This file is exec'ed by the spinoff system, allowing us to define -# values and behavior for this feature-set here in a way that can be -# type-checked alongside other project Python code. +# values and behavior for this feature-set here in a programmatic way +# that can also be type-checked alongside other project Python code. from batools.featureset import FeatureSet diff --git a/config/featuresets/featureset_std.py b/config/featuresets/featureset_std.py new file mode 100644 index 00000000..f94b144e --- /dev/null +++ b/config/featuresets/featureset_std.py @@ -0,0 +1,17 @@ +# Released under the MIT License. See LICENSE for details. +# +# pylint: disable=missing-docstring, invalid-name +from __future__ import annotations + +# This file is exec'ed by the spinoff system, allowing us to define +# values and behavior for this feature-set here in a programmatic way +# that can also be type-checked alongside other project Python code. + +from batools.featureset import FeatureSet + +# Grab the FeatureSet we should apply to. +fset = FeatureSet.get_active() + +fset.has_native_python_module = False + +fset.requirements = {'core', 'base', 'scene_v1', 'ui_v1'} diff --git a/config/featuresets/featureset_template_fs.py b/config/featuresets/featureset_template_fs.py index 9773718f..843df767 100644 --- a/config/featuresets/featureset_template_fs.py +++ b/config/featuresets/featureset_template_fs.py @@ -4,8 +4,8 @@ from __future__ import annotations # This file is exec'ed by the spinoff system, allowing us to define -# values and behavior for this feature-set here in a way that can be -# type-checked alongside other project Python code. +# values and behavior for this feature-set here in a programmatic way +# that can also be type-checked alongside other project Python code. from batools.featureset import FeatureSet diff --git a/config/featuresets/featureset_ui_v1.py b/config/featuresets/featureset_ui_v1.py index c1883593..ef301aae 100644 --- a/config/featuresets/featureset_ui_v1.py +++ b/config/featuresets/featureset_ui_v1.py @@ -4,8 +4,8 @@ from __future__ import annotations # This file is exec'ed by the spinoff system, allowing us to define -# values and behavior for this feature-set here in a way that can be -# type-checked alongside other project Python code. +# values and behavior for this feature-set here in a programmatic way +# that can also be type-checked alongside other project Python code. from batools.featureset import FeatureSet diff --git a/config/spinoffconfig.py b/config/spinoffconfig.py index abd43e46..6a4b757e 100644 --- a/config/spinoffconfig.py +++ b/config/spinoffconfig.py @@ -52,6 +52,7 @@ ctx.src_omit_paths = { 'ballisticakit-android/build', 'tools/spinoff', '.editorconfig', + 'src/assets/workspace', } # Use this to 'carve out' directories or exact file paths which will be diff --git a/src/assets/.asset_manifest_public.json b/src/assets/.asset_manifest_public.json index 8a787e24..682d700e 100644 --- a/src/assets/.asset_manifest_public.json +++ b/src/assets/.asset_manifest_public.json @@ -13,6 +13,7 @@ "ba_data/python/babase/__pycache__/_assetmanager.cpython-311.opt-1.pyc", "ba_data/python/babase/__pycache__/_asyncio.cpython-311.opt-1.pyc", "ba_data/python/babase/__pycache__/_cloud.cpython-311.opt-1.pyc", + "ba_data/python/babase/__pycache__/_env.cpython-311.opt-1.pyc", "ba_data/python/babase/__pycache__/_error.cpython-311.opt-1.pyc", "ba_data/python/babase/__pycache__/_general.cpython-311.opt-1.pyc", "ba_data/python/babase/__pycache__/_hooks.cpython-311.opt-1.pyc", @@ -37,6 +38,7 @@ "ba_data/python/babase/_assetmanager.py", "ba_data/python/babase/_asyncio.py", "ba_data/python/babase/_cloud.py", + "ba_data/python/babase/_env.py", "ba_data/python/babase/_error.py", "ba_data/python/babase/_general.py", "ba_data/python/babase/_hooks.py", diff --git a/src/assets/Makefile b/src/assets/Makefile index 2c526191..8cf277c9 100644 --- a/src/assets/Makefile +++ b/src/assets/Makefile @@ -146,6 +146,7 @@ SCRIPT_TARGETS_PY_PUBLIC = \ $(BUILD_DIR)/ba_data/python/babase/_assetmanager.py \ $(BUILD_DIR)/ba_data/python/babase/_asyncio.py \ $(BUILD_DIR)/ba_data/python/babase/_cloud.py \ + $(BUILD_DIR)/ba_data/python/babase/_env.py \ $(BUILD_DIR)/ba_data/python/babase/_error.py \ $(BUILD_DIR)/ba_data/python/babase/_general.py \ $(BUILD_DIR)/ba_data/python/babase/_hooks.py \ @@ -416,6 +417,7 @@ SCRIPT_TARGETS_PYC_PUBLIC = \ $(BUILD_DIR)/ba_data/python/babase/__pycache__/_assetmanager.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/babase/__pycache__/_asyncio.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/babase/__pycache__/_cloud.cpython-311.opt-1.pyc \ + $(BUILD_DIR)/ba_data/python/babase/__pycache__/_env.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/babase/__pycache__/_error.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/babase/__pycache__/_general.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/babase/__pycache__/_hooks.cpython-311.opt-1.pyc \ diff --git a/src/assets/ba_data/python/babase/_env.py b/src/assets/ba_data/python/babase/_env.py new file mode 100644 index 00000000..02c225e7 --- /dev/null +++ b/src/assets/ba_data/python/babase/_env.py @@ -0,0 +1,210 @@ +# Released under the MIT License. See LICENSE for details. +# +"""Environment related functionality.""" +from __future__ import annotations + +import sys +import signal +import logging +from typing import TYPE_CHECKING + +from efro.log import LogLevel + +if TYPE_CHECKING: + from typing import Any + from efro.log import LogEntry, LogHandler + +_g_babase_imported = False # pylint: disable=invalid-name +_g_babase_app_started = False # pylint: disable=invalid-name + + +def on_native_module_import() -> None: + """Called by _babase when it is imported; does some sanity checking/etc.""" + import _babase + import baenv + + global _g_babase_imported # pylint: disable=global-statement + + assert not _g_babase_imported + _g_babase_imported = True + + # If we have a log_handler set up, wire it up to feed _babase its output. + envconfig = baenv.get_config() + if envconfig.log_handler is not None: + _feed_logs_to_babase(envconfig.log_handler) + + env = _babase.pre_env() + + # Give a soft warning if we're being used with a different binary + # version than we were built for. + running_build: int = env['build_number'] + if running_build != baenv.TARGET_BALLISTICA_BUILD: + logging.warning( + 'These scripts are meant to be used with' + ' Ballistica build %d, but you are running build %d.' + " This might cause problems. Module path: '%s'.", + baenv.TARGET_BALLISTICA_BUILD, + running_build, + __file__, + ) + + debug_build = env['debug_build'] + + # We expect dev_mode on in debug builds and off otherwise; + # make noise if that's not the case. + if debug_build != sys.flags.dev_mode: + logging.warning( + 'Mismatch in ballistica debug_build %s' + ' and sys.flags.dev_mode %s; this may cause problems.', + debug_build, + sys.flags.dev_mode, + ) + + +def setup_env_for_app_run() -> None: + """Set stuff such as interrupt handlers for a run of the app.""" + import gc + import _babase + import baenv + + global _g_babase_app_started # pylint: disable=global-statement + + _g_babase_app_started = True + + assert _g_babase_imported + assert baenv.config_exists() + + # If we were unable to set paths earlier, complain now. + if baenv.g_paths_set_failed: + logging.warning( + 'Ballistica Python paths have not been set. This may cause' + ' problems. To ensure paths are set, run baenv.configure()' + ' before importing any ballistica modules.' + ) + + # Set up interrupt-signal handling. + + # Note: I've found we need to set up our C signal handling AFTER + # we've told Python to disable its own; otherwise (on Mac at least) + # it wipes out our existing C handler. + signal.signal(signal.SIGINT, signal.SIG_DFL) # Do default handling. + _babase.setup_sigint() + + # Turn off fancy-pants cyclic garbage-collection. We run it only at + # explicit times to avoid random hitches and keep things more + # deterministic. Non-reference-looped objects will still get cleaned + # up immediately, so we should try to structure things to avoid + # reference loops (just like Swift, ObjC, etc). + + # FIXME - move this to Python bootstrapping code. or perhaps disable + # it completely since we've got more bg stuff happening now?... + # (but put safeguards in place to time/minimize gc pauses). + gc.disable() + + # pylint: disable=c-extension-no-member + if not TYPE_CHECKING: + import __main__ + + # Clear out the standard quit/exit messages since they don't + # work in our embedded situation (should revisit this once we're + # usable from a standard interpreter). Note that these don't + # exist in the first place for our monolithic builds which don't + # use site.py. + for attr in ('quit', 'exit'): + if hasattr(__main__.__builtins__, attr): + delattr(__main__.__builtins__, attr) + + # Also replace standard interactive help with our simplified + # non-blocking one which is more friendly to cloud/in-app console + # situations. + __main__.__builtins__.help = _CustomHelper() + + # On Windows I'm seeing the following error creating asyncio loops + # in background threads with the default proactor setup: + + # ValueError: set_wakeup_fd only works in main thread of the main + # interpreter. + + # So let's explicitly request selector loops. Interestingly this + # error only started showing up once I moved Python init to the main + # thread; previously the various asyncio bg thread loops were + # working fine (maybe something caused them to default to selector + # in that case?.. + if sys.platform == 'win32': + import asyncio + + asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) + + +def _feed_logs_to_babase(log_handler: LogHandler) -> None: + """Route log/print output to internal ballistica console/etc.""" + import _babase + + def _on_log(entry: LogEntry) -> None: + # Forward this along to the engine to display in the in-app + # console, in the Android log, etc. + _babase.display_log( + name=entry.name, + level=entry.level.name, + message=entry.message, + ) + + # We also want to feed some logs to the old v1-cloud-log system. + # Let's go with anything warning or higher as well as the + # stdout/stderr log messages that babase.app.log_handler creates + # for us. We should retire or upgrade this system at some point. + if entry.level.value >= LogLevel.WARNING.value or entry.name in ( + 'stdout', + 'stderr', + ): + _babase.v1_cloud_log(entry.message) + + # Add our callback and also feed it all entries already in the + # cache. This will feed the engine any logs that happened between + # baenv.configure() and now. + + # FIXME: while this works for now, the downside is that these + # callbacks fire in a bg thread so certain things like android + # logging will be delayed compared to code that uses native logging + # calls directly. Perhaps we should add some sort of 'immediate' + # callback option to better handle such cases (similar to the + # immediate echofile stderr print that already occurs). + log_handler.add_callback(_on_log, feed_existing_logs=True) + + +class _CustomHelper: + """Replacement 'help' that behaves better for our setup.""" + + def __repr__(self) -> str: + return 'Type help(object) for help about object.' + + def __call__(self, *args: Any, **kwds: Any) -> Any: + # We get an ugly error importing pydoc on our embedded platforms + # due to _sysconfigdata_xxx.py not being present (but then + # things mostly work). Let's get the ugly error out of the way + # explicitly. + + # FIXME: we shouldn't be seeing this error anymore. Should + # revisit this. + import sysconfig + + try: + # This errors once but seems to run cleanly after, so let's + # get the error out of the way. + sysconfig.get_path('stdlib') + except ModuleNotFoundError: + pass + + import pydoc + + # Disable pager and interactive help since neither works well + # with our funky multi-threaded setup or in-game/cloud consoles. + # Let's just do simple text dumps. + pydoc.pager = pydoc.plainpager + if not args and not kwds: + print( + 'Interactive help is not available in this environment.\n' + 'Type help(object) for help about object.' + ) + return None + return pydoc.help(*args, **kwds) diff --git a/src/assets/ba_data/python/baenv.py b/src/assets/ba_data/python/baenv.py index 4d05d5b1..3f467a07 100644 --- a/src/assets/ba_data/python/baenv.py +++ b/src/assets/ba_data/python/baenv.py @@ -16,7 +16,6 @@ from __future__ import annotations import os import sys -import signal import logging from pathlib import Path from dataclasses import dataclass @@ -27,17 +26,15 @@ from efro.log import setup_logging, LogLevel if TYPE_CHECKING: from typing import Any - from efro.log import LogEntry, LogHandler + from efro.log import LogHandler # Build number and version of the ballistica binary we expect to be # using. -TARGET_BALLISTICA_BUILD = 21030 +TARGET_BALLISTICA_BUILD = 21032 TARGET_BALLISTICA_VERSION = '1.7.20' _g_env_config: EnvConfig | None = None -_g_babase_imported = False # pylint: disable=invalid-name -_g_babase_app_started = False # pylint: disable=invalid-name -_g_paths_set_failed = False # pylint: disable=invalid-name +g_paths_set_failed = False # pylint: disable=invalid-name @dataclass @@ -134,15 +131,15 @@ def configure( # If _babase has already been imported, there's not much we can do # at this point aside from complain and inform for next time. - if _g_babase_imported: + if '_babase' in sys.modules: app_python_dir = user_python_dir = site_python_dir = None # We don't actually complain yet here; we simply take note # that we weren't able to set paths. Then we complain if/when # the app is started. This way, non-app uses of babase won't be # filled with unnecessary warnings. - global _g_paths_set_failed # pylint: disable=global-statement - _g_paths_set_failed = True + global g_paths_set_failed # pylint: disable=global-statement + g_paths_set_failed = True else: # Ok; _babase hasn't been imported yet so we can muck with @@ -231,189 +228,7 @@ def on_babase_import() -> None: on_babase_start_app(). This allows bits of _babase to be used under existing environments without messing things up too badly. """ - import _babase - - global _g_babase_imported # pylint: disable=global-statement - - assert not _g_babase_imported - _g_babase_imported = True - - # If we have a log_handler set up, wire it up to feed _babase its output. - envconfig = get_config() - if envconfig.log_handler is not None: - _feed_logs_to_babase(envconfig.log_handler) - - env = _babase.pre_env() - - # Give a soft warning if we're being used with a different binary - # version than we were built for. - running_build: int = env['build_number'] - if running_build != TARGET_BALLISTICA_BUILD: - logging.warning( - 'These scripts are meant to be used with' - ' Ballistica build %d, but you are running build %d.' - " This might cause problems. Module path: '%s'.", - TARGET_BALLISTICA_BUILD, - running_build, - __file__, - ) - - debug_build = env['debug_build'] - - # We expect dev_mode on in debug builds and off otherwise; - # make noise if that's not the case. - if debug_build != sys.flags.dev_mode: - logging.warning( - 'Mismatch in ballistica debug_build %s' - ' and sys.flags.dev_mode %s; this may cause problems.', - debug_build, - sys.flags.dev_mode, - ) def on_babase_start_app() -> None: """Called when ballistica's babase module is spinning up an app.""" - import gc - import _babase - - global _g_babase_app_started # pylint: disable=global-statement - - _g_babase_app_started = True - - assert _g_babase_imported - assert config_exists() - - # If we were unable to set paths earlier, complain now. - if _g_paths_set_failed: - logging.warning( - 'Ballistica Python paths have not been set. This may cause' - ' problems. To ensure paths are set, run baenv.configure()' - ' before importing any ballistica modules.' - ) - - # Set up interrupt-signal handling. - - # Note: I've found we need to set up our C signal handling AFTER - # we've told Python to disable its own; otherwise (on Mac at least) - # it wipes out our existing C handler. - signal.signal(signal.SIGINT, signal.SIG_DFL) # Do default handling. - _babase.setup_sigint() - - # Turn off fancy-pants cyclic garbage-collection. We run it only at - # explicit times to avoid random hitches and keep things more - # deterministic. Non-reference-looped objects will still get cleaned - # up immediately, so we should try to structure things to avoid - # reference loops (just like Swift, ObjC, etc). - - # FIXME - move this to Python bootstrapping code. or perhaps disable - # it completely since we've got more bg stuff happening now?... - # (but put safeguards in place to time/minimize gc pauses). - gc.disable() - - # pylint: disable=c-extension-no-member - if not TYPE_CHECKING: - import __main__ - - # Clear out the standard quit/exit messages since they don't - # work in our embedded situation (should revisit this once we're - # usable from a standard interpreter). Note that these don't - # exist in the first place for our monolithic builds which don't - # use site.py. - for attr in ('quit', 'exit'): - if hasattr(__main__.__builtins__, attr): - delattr(__main__.__builtins__, attr) - - # Also replace standard interactive help with our simplified - # non-blocking one which is more friendly to cloud/in-app console - # situations. - __main__.__builtins__.help = _CustomHelper() - - # On Windows I'm seeing the following error creating asyncio loops - # in background threads with the default proactor setup: - - # ValueError: set_wakeup_fd only works in main thread of the main - # interpreter. - - # So let's explicitly request selector loops. Interestingly this - # error only started showing up once I moved Python init to the main - # thread; previously the various asyncio bg thread loops were - # working fine (maybe something caused them to default to selector - # in that case?.. - if sys.platform == 'win32': - import asyncio - - asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) - - -class _CustomHelper: - """Replacement 'help' that behaves better for our setup.""" - - def __repr__(self) -> str: - return 'Type help(object) for help about object.' - - def __call__(self, *args: Any, **kwds: Any) -> Any: - # We get an ugly error importing pydoc on our embedded platforms - # due to _sysconfigdata_xxx.py not being present (but then - # things mostly work). Let's get the ugly error out of the way - # explicitly. - - # FIXME: we shouldn't be seeing this error anymore. Should - # revisit this. - import sysconfig - - try: - # This errors once but seems to run cleanly after, so let's - # get the error out of the way. - sysconfig.get_path('stdlib') - except ModuleNotFoundError: - pass - - import pydoc - - # Disable pager and interactive help since neither works well - # with our funky multi-threaded setup or in-game/cloud consoles. - # Let's just do simple text dumps. - pydoc.pager = pydoc.plainpager - if not args and not kwds: - print( - 'Interactive help is not available in this environment.\n' - 'Type help(object) for help about object.' - ) - return None - return pydoc.help(*args, **kwds) - - -def _feed_logs_to_babase(log_handler: LogHandler) -> None: - """Route log/print output to internal ballistica console/etc.""" - import _babase - - def _on_log(entry: LogEntry) -> None: - # Forward this along to the engine to display in the in-app - # console, in the Android log, etc. - _babase.display_log( - name=entry.name, - level=entry.level.name, - message=entry.message, - ) - - # We also want to feed some logs to the old v1-cloud-log system. - # Let's go with anything warning or higher as well as the - # stdout/stderr log messages that babase.app.log_handler creates - # for us. We should retire or upgrade this system at some point. - if entry.level.value >= LogLevel.WARNING.value or entry.name in ( - 'stdout', - 'stderr', - ): - _babase.v1_cloud_log(entry.message) - - # Add our callback and also feed it all entries already in the - # cache. This will feed the engine any logs that happened between - # baenv.configure() and now. - - # FIXME: while this works for now, the downside is that these - # callbacks fire in a bg thread so certain things like android - # logging will be delayed compared to code that uses native logging - # calls directly. Perhaps we should add some sort of 'immediate' - # callback option to better handle such cases (similar to the - # immediate echofile stderr print that already occurs). - log_handler.add_callback(_on_log, feed_existing_logs=True) diff --git a/src/ballistica/base/base.cc b/src/ballistica/base/base.cc index a0dc2fc9..1d47f6b1 100644 --- a/src/ballistica/base/base.cc +++ b/src/ballistica/base/base.cc @@ -113,6 +113,14 @@ void BaseFeatureSet::OnModuleExec(PyObject* module) { // let baenv know it can now feed us logs and run some checks. g_core->python->RunBaEnvOnBaBaseImport(); + // Run some sanity checks/etc. + auto result = g_base->python->objs() + .Get(BasePython::ObjID::kOnNativeModuleImportCall) + .Call(); + if (!result.Exists()) { + FatalError("babase._env.on_native_module_import() call failed."); + } + // ..and because baenv is now feeding us logs, we can push any logs through // that we've been holding on to. g_core->python->EnablePythonLoggingCalls(); diff --git a/src/ballistica/base/python/base_python.cc b/src/ballistica/base/python/base_python.cc index 59983154..e95c11a1 100644 --- a/src/ballistica/base/python/base_python.cc +++ b/src/ballistica/base/python/base_python.cc @@ -123,6 +123,14 @@ void BasePython::OnMainThreadStartApp() { if (!result.Exists()) { FatalError("baenv.on_babase_start_app() failed."); } + + // Set up some env stuff (interrupt handlers, etc.) + result = g_base->python->objs() + .Get(BasePython::ObjID::kSetupEnvForAppRunCall) + .Call(); + if (!result.Exists()) { + FatalError("babase._env.setup_env_for_app_run() failed."); + } } void BasePython::OnAppStart() { diff --git a/src/ballistica/base/python/base_python.h b/src/ballistica/base/python/base_python.h index a97b9f3d..63549daa 100644 --- a/src/ballistica/base/python/base_python.h +++ b/src/ballistica/base/python/base_python.h @@ -101,6 +101,8 @@ class BasePython { kOnTooManyFileDescriptorsCall, kPreEnv, kOpenURLWithWebBrowserModuleCall, + kOnNativeModuleImportCall, + kSetupEnvForAppRunCall, kLast // Sentinel; must be at end. }; diff --git a/src/ballistica/shared/ballistica.cc b/src/ballistica/shared/ballistica.cc index ad1c21b7..a8a65042 100644 --- a/src/ballistica/shared/ballistica.cc +++ b/src/ballistica/shared/ballistica.cc @@ -39,7 +39,7 @@ auto main(int argc, char** argv) -> int { namespace ballistica { // These are set automatically via script; don't modify them here. -const int kEngineBuildNumber = 21030; +const int kEngineBuildNumber = 21032; const char* kEngineVersion = "1.7.20"; auto MonolithicMain(const core::CoreConfig& core_config) -> int { diff --git a/src/meta/Makefile b/src/meta/Makefile index 7a511225..7dc98e8b 100644 --- a/src/meta/Makefile +++ b/src/meta/Makefile @@ -33,7 +33,7 @@ sources: \ $(PROJ_SRC_DIR)/ballistica/template_fs/mgen/pyembed/binding_template_fs.inc \ $(PROJ_SRC_DIR)/ballistica/ui_v1/mgen/pyembed/binding_ui_v1.inc -$(PROJ_SRC_DIR)/ballistica/core/mgen/python_modules_monolithic.h : $(PROJ_DIR)/config/featuresets/featureset_base.py $(PROJ_DIR)/config/featuresets/featureset_classic.py $(PROJ_DIR)/config/featuresets/featureset_core.py $(PROJ_DIR)/config/featuresets/featureset_plus.py $(PROJ_DIR)/config/featuresets/featureset_scene_v1.py $(PROJ_DIR)/config/featuresets/featureset_template_fs.py $(PROJ_DIR)/config/featuresets/featureset_ui_v1.py +$(PROJ_SRC_DIR)/ballistica/core/mgen/python_modules_monolithic.h : $(PROJ_DIR)/config/featuresets/featureset_base.py $(PROJ_DIR)/config/featuresets/featureset_classic.py $(PROJ_DIR)/config/featuresets/featureset_core.py $(PROJ_DIR)/config/featuresets/featureset_plus.py $(PROJ_DIR)/config/featuresets/featureset_scene_v1.py $(PROJ_DIR)/config/featuresets/featureset_std.py $(PROJ_DIR)/config/featuresets/featureset_template_fs.py $(PROJ_DIR)/config/featuresets/featureset_ui_v1.py @$(PCOMMAND) gen_monolithic_register_modules $@ $(PROJ_SRC_DIR)/ballistica/base/mgen/pyembed/binding_base.inc : babasemeta/pyembed/binding_base.py diff --git a/src/meta/babasemeta/pyembed/binding_base.py b/src/meta/babasemeta/pyembed/binding_base.py index 03f04a2c..baeb3206 100644 --- a/src/meta/babasemeta/pyembed/binding_base.py +++ b/src/meta/babasemeta/pyembed/binding_base.py @@ -10,6 +10,7 @@ from babase import _language from babase import _apputils from babase._mgen import enums from babase import _hooks +from babase import _env # The C++ layer looks for this variable: values = [ @@ -85,4 +86,6 @@ values = [ _hooks.login_adapter_get_sign_in_token_response, # kLoginAdapterGetSignInTokenResponseCall _hooks.open_url_with_webbrowser_module, # kOpenURLWithWebBrowserModuleCall _apputils.on_too_many_file_descriptors, # kOnTooManyFileDescriptorsCall + _env.on_native_module_import, # kOnNativeModuleImportCall + _env.setup_env_for_app_run, # kSetupEnvForAppRunCall ] diff --git a/tools/batools/dummymodule.py b/tools/batools/dummymodule.py index ddedd234..7fe8f064 100755 --- a/tools/batools/dummymodule.py +++ b/tools/batools/dummymodule.py @@ -953,7 +953,6 @@ def generate(projroot: str) -> None: f'import sys\n' f'sys.path.append("build/assets/ba_data/python")\n' f'sys.path.append("{toolsdir}")\n' - f'import _babase\n' f'from batools import dummymodule\n' ) @@ -972,7 +971,6 @@ def generate(projroot: str) -> None: f'dummymodule.Generator(modulename="{mname}",' f' outfilename="{outfilename}").run()\n' ) - assert gencount # Launch ballisticakit and exec ourself from within it. print( diff --git a/tools/batools/featureset.py b/tools/batools/featureset.py index dedeb8df..c1d13e8b 100644 --- a/tools/batools/featureset.py +++ b/tools/batools/featureset.py @@ -140,9 +140,14 @@ class FeatureSet: @property def name_python_package_meta(self) -> str: - """The name of our meta python package.""" + """The name of our meta Python package.""" return f'ba{self.name_compact}meta' + @property + def name_python_package_tests(self) -> str: + """The name of our Python tests package.""" + return f'test_ba{self.name_compact}' + @property def name_python_binary_module(self) -> str: """Python binary module name (foo_bar -> _bafoobar).""" diff --git a/tools/batools/pcommand2.py b/tools/batools/pcommand2.py index d1d5b979..0965fc74 100644 --- a/tools/batools/pcommand2.py +++ b/tools/batools/pcommand2.py @@ -333,7 +333,9 @@ def spinoff_test() -> None: '--featuresets', 'none', ] - print(Clr.MAG + ' '.join(cmd) + Clr.RST) + # 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, @@ -341,6 +343,37 @@ def spinoff_test() -> None: os.makedirs(path, exist_ok=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) + # 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'), + 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/batools/spinoff/_context.py b/tools/batools/spinoff/_context.py index b2591a70..23f3a55e 100644 --- a/tools/batools/spinoff/_context.py +++ b/tools/batools/spinoff/_context.py @@ -332,6 +332,10 @@ class SpinoffContext: fsmetapackagename = featureset.name_python_package_meta paths.add(f'src/meta/{fsmetapackagename}') + # Omit its tests package. + fstestspackagename = featureset.name_python_package_tests + paths.add(f'tests/{fstestspackagename}') + @classmethod def get_active(cls) -> SpinoffContext: """Return the context currently running.""" @@ -1757,8 +1761,9 @@ class SpinoffContext: # pylint: disable=too-many-locals # Ok, *something* differs from our cache. Need to take a closer look. + + # With no dst we have to do the copy of course. if not dst_exists: - # With no dst gotta do a copy of course. self._src_copy_entities.add(src_path) return @@ -1801,46 +1806,53 @@ class SpinoffContext: ) return - # Let's filter the src file and if it matches dst we can just - # re-grab our cache info and call it a day. - if self._should_filter_src_file(src_path): - encoding = self._encoding_for_file(src_path_full) - with open(src_path_full, 'rb') as infile: - try: - src_data = self._filter_file( - src_path, infile.read().decode(encoding) - ) - except Exception: - print(f"Error decoding/filtering file: '{src_path}'.") - raise - with open(dst_path_full, 'rb') as infile: - try: - dst_data = infile.read().decode(encoding) - except Exception: - print(f"Error decoding file: '{dst_path}'.") - raise - results_are_same = src_data == dst_data + is_project_file = self._is_project_file(src_path) - # Bytes versions are only used very rarely by 'backport' - # command so let's lazy compute them here. - src_datab = dst_datab = None + if is_project_file: + # Project files apply arbitrary logic on top of our + # copying/filtering (which we cannot check here) so we can + # never assume results are unchanging. + results_are_same = False else: - # Ok our src isn't filtered; can be a bit more streamlined. - with open(src_path_full, 'rb') as infile: - src_datab = infile.read() - with open(dst_path_full, 'rb') as infile: - dst_datab = infile.read() - results_are_same = src_datab == dst_datab + # Let's filter the src file and if it matches dst we can just + # re-grab our cache info and call it a day. + if self._should_filter_src_file(src_path): + encoding = self._encoding_for_file(src_path_full) + with open(src_path_full, 'rb') as infile: + try: + src_data = self._filter_file( + src_path, infile.read().decode(encoding) + ) + except Exception: + print(f"Error decoding/filtering file: '{src_path}'.") + raise + with open(dst_path_full, 'rb') as infile: + try: + dst_data = infile.read().decode(encoding) + except Exception: + print(f"Error decoding file: '{dst_path}'.") + raise + results_are_same = src_data == dst_data - # No string versions needed in this case. - src_data = dst_data = None + # Bytes versions are only used very rarely by 'backport' + # command so let's lazy compute them here. + src_datab = dst_datab = None + else: + # Ok our src isn't filtered; can be a bit more streamlined. + with open(src_path_full, 'rb') as infile: + src_datab = infile.read() + with open(dst_path_full, 'rb') as infile: + dst_datab = infile.read() + results_are_same = src_datab == dst_datab + + # No string versions needed in this case. + src_data = dst_data = None if results_are_same: # Things match; just update the times we've got recorded # for these fellas. self._src_recache_entities.add(src_path) else: - # Ok, something legit changed. if (os.path.getsize(dst_path_full) == dst_entity.dst_size) and ( os.path.getmtime(dst_path_full) == dst_entity.dst_mtime ): diff --git a/tools/batools/spinoff/_main.py b/tools/batools/spinoff/_main.py index befe9a5a..b5601969 100644 --- a/tools/batools/spinoff/_main.py +++ b/tools/batools/spinoff/_main.py @@ -215,7 +215,7 @@ def _do_create(src_root: str | None, dst_root: str) -> None: doneclr = Clr.BLU if noninteractive else Clr.GRN print( f'{doneclr}{Clr.BLD}Spinoff dst project created at' - f' {Clr.RST}{Clr.BLD}{path}{Clr.RST}{doneclr}.{Clr.RST}' + f' {Clr.RST}{Clr.BLD}{path}{Clr.RST}{doneclr}.{Clr.RST}', ) if not noninteractive: print(