From 77149db01967d69c295e13061c1361bcccadbd7e Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Thu, 28 May 2026 08:40:40 +0800 Subject: [PATCH 1/5] Adopt os-log based logging. --- .gitignore | 2 + src/briefcase/bootstraps/toga.py | 3 +- src/briefcase/integrations/cookiecutter.py | 5 + src/briefcase/platforms/iOS/xcode.py | 30 ++++- src/briefcase/platforms/macOS/__init__.py | 5 +- src/briefcase/platforms/macOS/filters.py | 6 +- tests/commands/new/test_build_gui_context.py | 3 +- .../test_PythonVersionExtension.py | 24 ++++ tests/platforms/iOS/xcode/test_run.py | 121 +++++++++++------- tests/platforms/macOS/app/test_run.py | 66 +++++++--- .../macOS/test_macOS_log_clean_filter.py | 24 +++- tests/platforms/macOS/xcode/test_run.py | 44 +++++-- 12 files changed, 243 insertions(+), 90 deletions(-) diff --git a/.gitignore b/.gitignore index fafefb618b..56daf881d6 100644 --- a/.gitignore +++ b/.gitignore @@ -50,8 +50,10 @@ logs/ .specify/templates .specify/extensions.yml .specify/extensions +.specify/ .specify/init-options.json .specify/integration.json .specify/integrations .specify/workflows .specify/workflow.yml +specs diff --git a/src/briefcase/bootstraps/toga.py b/src/briefcase/bootstraps/toga.py index 671e7aad6a..3a4477e86e 100644 --- a/src/briefcase/bootstraps/toga.py +++ b/src/briefcase/bootstraps/toga.py @@ -51,7 +51,7 @@ def pyproject_table_macOS(self): universal_build = true requires = [ "toga-cocoa~=0.5.0", - "std-nslog~=1.0.3", + "std-nslog~=2.0.0", ] """ @@ -206,7 +206,6 @@ def pyproject_table_iOS(self): return """\ requires = [ "toga-iOS~=0.5.0", - "std-nslog~=1.0.3", ] """ diff --git a/src/briefcase/integrations/cookiecutter.py b/src/briefcase/integrations/cookiecutter.py index 0a1e592d3e..1b4ed971fe 100644 --- a/src/briefcase/integrations/cookiecutter.py +++ b/src/briefcase/integrations/cookiecutter.py @@ -23,6 +23,10 @@ def py_libtag(obj): """A Python version library tag (311)""" return "".join(obj.split(".")[:2]) + def minor_version(obj): + """The Python minor version, as an integer (e.g., 11)""" + return int(obj.split(".")[1]) + def nuget_version(obj): """A Python version in Nuget format (3.14.0-rc1).""" parts = obj.split(".")[:3] @@ -32,6 +36,7 @@ def nuget_version(obj): environment.filters["py_tag"] = py_tag environment.filters["py_libtag"] = py_libtag + environment.filters["minor_version"] = minor_version environment.filters["nuget_version"] = nuget_version diff --git a/src/briefcase/platforms/iOS/xcode.py b/src/briefcase/platforms/iOS/xcode.py index 39a367214f..9fbd2647a9 100644 --- a/src/briefcase/platforms/iOS/xcode.py +++ b/src/briefcase/platforms/iOS/xcode.py @@ -625,7 +625,13 @@ def run_app( "Uninstalling any existing app version..." ) as keep_alive, self.tools.subprocess.Popen( - ["xcrun", "simctl", "uninstall", udid, app.bundle_identifier] + [ + "xcrun", + "simctl", + "uninstall", + udid, + app.bundle_identifier, + ] ) as uninstall_popen, ): while (ret_code := uninstall_popen.poll()) is None: @@ -641,7 +647,13 @@ def run_app( with ( self.console.wait_bar(f"Installing new {label} version...") as keep_alive, self.tools.subprocess.Popen( - ["xcrun", "simctl", "install", udid, self.binary_path(app)] + [ + "xcrun", + "simctl", + "install", + udid, + self.binary_path(app), + ] ) as install_popen, ): while (ret_code := install_popen.poll()) is None: @@ -661,7 +673,8 @@ def run_app( # and for native NSLog() calls in the bootstrap binary # Case (2) works when the standard library is dynamically linked, # and ctypes (which handles the NSLog integration) is an - # extension module. + # extension module. It also catches the case for the CPython + # builtin behavior of redirecting to the system log. # It's not enough to filter on *just* the processImagePath, # as the process will generate lots of system-level messages. # We can't filter on *just* the senderImagePath, because other @@ -679,10 +692,13 @@ def run_app( "--predicate", ( f'senderImagePath ENDSWITH "/{app.formal_name}"' - f' OR (processImagePath ENDSWITH "/{app.formal_name}"' - ' AND (senderImagePath ENDSWITH "-iphonesimulator.so"' - ' OR senderImagePath ENDSWITH "-iphonesimulator.dylib"' - ' OR senderImagePath ENDSWITH "_ctypes.framework/_ctypes"))' + f'OR (processImagePath ENDSWITH "/{app.formal_name}"' + ' AND (senderImagePath ENDSWITH "-iphonesimulator.so"' + ' OR senderImagePath ENDSWITH "-iphonesimulator.dylib"' + ' OR senderImagePath ENDSWITH "_ctypes.framework/_ctypes"' + ' OR senderImagePath ENDSWITH "/Python"' + " )" + ")" ), ], stdout=subprocess.PIPE, diff --git a/src/briefcase/platforms/macOS/__init__.py b/src/briefcase/platforms/macOS/__init__.py index ac66e254ea..b4c7d3752f 100644 --- a/src/briefcase/platforms/macOS/__init__.py +++ b/src/briefcase/platforms/macOS/__init__.py @@ -549,7 +549,10 @@ def run_gui_app( ( f'senderImagePath=="{sender}"' f' OR (processImagePath=="{sender}"' - ' AND senderImagePath=="/usr/lib/libffi.dylib")' + ' AND (senderImagePath=="/usr/lib/libffi.dylib" ' + ' OR senderImagePath ENDSWITH "/Python" ' + ' OR senderImagePath ENDSWITH ".abi3.so")' + " )" ), ], stdout=subprocess.PIPE, diff --git a/src/briefcase/platforms/macOS/filters.py b/src/briefcase/platforms/macOS/filters.py index 46833b0e3b..7e8e61ba34 100644 --- a/src/briefcase/platforms/macOS/filters.py +++ b/src/briefcase/platforms/macOS/filters.py @@ -4,8 +4,10 @@ MACOS_LOG_PREFIX_REGEX = re.compile( r"\d{4}-\d{2}-\d{2} (?P\d{2}:\d{2}:\d{2}.\d{3}) Df (.*?)\[.*?:.*?\]" - r"(?P( \(libffi\.dylib\))|(" - r" \(_ctypes(\.cpython-3\d{1,2}-.*?\.(so|dylib))?\)))? (?P.*)" + r"(?P( \(libffi\.dylib\))|( \(Python\))" + r"|( \(_ctypes(\.cpython-3\d{1,2}-.*?\.(so|dylib))?\))" + r"|( \(_oslog_shim\..*?\.(so|dylib)?\))" + r")? (?P.*)" ) diff --git a/tests/commands/new/test_build_gui_context.py b/tests/commands/new/test_build_gui_context.py index f412cdc327..b9a34c3806 100644 --- a/tests/commands/new/test_build_gui_context.py +++ b/tests/commands/new/test_build_gui_context.py @@ -66,7 +66,7 @@ def main(): universal_build = true requires = [ "toga-cocoa~=0.5.0", - "std-nslog~=1.0.3", + "std-nslog~=2.0.0", ] """, "pyproject_table_linux": """\ @@ -203,7 +203,6 @@ def main(): "pyproject_table_iOS": """\ requires = [ "toga-iOS~=0.5.0", - "std-nslog~=1.0.3", ] """, "pyproject_table_android": '''\ diff --git a/tests/integrations/cookiecutter/test_PythonVersionExtension.py b/tests/integrations/cookiecutter/test_PythonVersionExtension.py index a1390ddff8..c5fe749485 100644 --- a/tests/integrations/cookiecutter/test_PythonVersionExtension.py +++ b/tests/integrations/cookiecutter/test_PythonVersionExtension.py @@ -53,6 +53,30 @@ def test_py_libtag(value, expected): assert env.filters["py_libtag"](value) == expected +@pytest.mark.parametrize( + ("value", "expected"), + [ + # Single digit minor + ("3.8.4.dev5", 8), + ("3.8.4a1", 8), + ("3.8.4b2", 8), + ("3.8.4rc3", 8), + ("3.8.4.post6", 8), + # Two digit minor + ("3.11.4.dev5", 11), + ("3.11.4a1", 11), + ("3.11.4b2", 11), + ("3.11.4rc3", 11), + ("3.11.4.post6", 11), + ], +) +def test_minor_version(value, expected): + env = MagicMock() + env.filters = {} + PythonVersionExtension(env) + assert env.filters["minor_version"](value) == expected + + @pytest.mark.parametrize( ("value", "expected"), [ diff --git a/tests/platforms/iOS/xcode/test_run.py b/tests/platforms/iOS/xcode/test_run.py index 60885e9c52..70df8bb008 100644 --- a/tests/platforms/iOS/xcode/test_run.py +++ b/tests/platforms/iOS/xcode/test_run.py @@ -179,10 +179,13 @@ def test_run_app_simulator_booted(run_command, first_app_config, tmp_path): "compact", "--predicate", 'senderImagePath ENDSWITH "/First App"' - ' OR (processImagePath ENDSWITH "/First App"' - ' AND (senderImagePath ENDSWITH "-iphonesimulator.so"' - ' OR senderImagePath ENDSWITH "-iphonesimulator.dylib"' - ' OR senderImagePath ENDSWITH "_ctypes.framework/_ctypes"))', + 'OR (processImagePath ENDSWITH "/First App"' + ' AND (senderImagePath ENDSWITH "-iphonesimulator.so"' + ' OR senderImagePath ENDSWITH "-iphonesimulator.dylib"' + ' OR senderImagePath ENDSWITH "_ctypes.framework/_ctypes"' + ' OR senderImagePath ENDSWITH "/Python"' + " )" + ")", ], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, @@ -310,10 +313,13 @@ def test_run_app_simulator_booted_underscore( "compact", "--predicate", 'senderImagePath ENDSWITH "/First App"' - ' OR (processImagePath ENDSWITH "/First App"' - ' AND (senderImagePath ENDSWITH "-iphonesimulator.so"' - ' OR senderImagePath ENDSWITH "-iphonesimulator.dylib"' - ' OR senderImagePath ENDSWITH "_ctypes.framework/_ctypes"))', + 'OR (processImagePath ENDSWITH "/First App"' + ' AND (senderImagePath ENDSWITH "-iphonesimulator.so"' + ' OR senderImagePath ENDSWITH "-iphonesimulator.dylib"' + ' OR senderImagePath ENDSWITH "_ctypes.framework/_ctypes"' + ' OR senderImagePath ENDSWITH "/Python"' + " )" + ")", ], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, @@ -438,10 +444,13 @@ def test_run_app_with_passthrough(run_command, first_app_config, tmp_path): "compact", "--predicate", 'senderImagePath ENDSWITH "/First App"' - ' OR (processImagePath ENDSWITH "/First App"' - ' AND (senderImagePath ENDSWITH "-iphonesimulator.so"' - ' OR senderImagePath ENDSWITH "-iphonesimulator.dylib"' - ' OR senderImagePath ENDSWITH "_ctypes.framework/_ctypes"))', + 'OR (processImagePath ENDSWITH "/First App"' + ' AND (senderImagePath ENDSWITH "-iphonesimulator.so"' + ' OR senderImagePath ENDSWITH "-iphonesimulator.dylib"' + ' OR senderImagePath ENDSWITH "_ctypes.framework/_ctypes"' + ' OR senderImagePath ENDSWITH "/Python"' + " )" + ")", ], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, @@ -573,10 +582,13 @@ def test_run_app_simulator_shut_down( "compact", "--predicate", 'senderImagePath ENDSWITH "/First App"' - ' OR (processImagePath ENDSWITH "/First App"' - ' AND (senderImagePath ENDSWITH "-iphonesimulator.so"' - ' OR senderImagePath ENDSWITH "-iphonesimulator.dylib"' - ' OR senderImagePath ENDSWITH "_ctypes.framework/_ctypes"))', + 'OR (processImagePath ENDSWITH "/First App"' + ' AND (senderImagePath ENDSWITH "-iphonesimulator.so"' + ' OR senderImagePath ENDSWITH "-iphonesimulator.dylib"' + ' OR senderImagePath ENDSWITH "_ctypes.framework/_ctypes"' + ' OR senderImagePath ENDSWITH "/Python"' + " )" + ")", ], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, @@ -714,10 +726,13 @@ def test_run_app_simulator_shutting_down(run_command, first_app_config, tmp_path "compact", "--predicate", 'senderImagePath ENDSWITH "/First App"' - ' OR (processImagePath ENDSWITH "/First App"' - ' AND (senderImagePath ENDSWITH "-iphonesimulator.so"' - ' OR senderImagePath ENDSWITH "-iphonesimulator.dylib"' - ' OR senderImagePath ENDSWITH "_ctypes.framework/_ctypes"))', + 'OR (processImagePath ENDSWITH "/First App"' + ' AND (senderImagePath ENDSWITH "-iphonesimulator.so"' + ' OR senderImagePath ENDSWITH "-iphonesimulator.dylib"' + ' OR senderImagePath ENDSWITH "_ctypes.framework/_ctypes"' + ' OR senderImagePath ENDSWITH "/Python"' + " )" + ")", ], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, @@ -1090,10 +1105,13 @@ def test_run_app_simulator_launch_failure(run_command, first_app_config, tmp_pat "compact", "--predicate", 'senderImagePath ENDSWITH "/First App"' - ' OR (processImagePath ENDSWITH "/First App"' - ' AND (senderImagePath ENDSWITH "-iphonesimulator.so"' - ' OR senderImagePath ENDSWITH "-iphonesimulator.dylib"' - ' OR senderImagePath ENDSWITH "_ctypes.framework/_ctypes"))', + 'OR (processImagePath ENDSWITH "/First App"' + ' AND (senderImagePath ENDSWITH "-iphonesimulator.so"' + ' OR senderImagePath ENDSWITH "-iphonesimulator.dylib"' + ' OR senderImagePath ENDSWITH "_ctypes.framework/_ctypes"' + ' OR senderImagePath ENDSWITH "/Python"' + " )" + ")", ], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, @@ -1211,10 +1229,13 @@ def test_run_app_simulator_no_pid(run_command, first_app_config, tmp_path): "compact", "--predicate", 'senderImagePath ENDSWITH "/First App"' - ' OR (processImagePath ENDSWITH "/First App"' - ' AND (senderImagePath ENDSWITH "-iphonesimulator.so"' - ' OR senderImagePath ENDSWITH "-iphonesimulator.dylib"' - ' OR senderImagePath ENDSWITH "_ctypes.framework/_ctypes"))', + 'OR (processImagePath ENDSWITH "/First App"' + ' AND (senderImagePath ENDSWITH "-iphonesimulator.so"' + ' OR senderImagePath ENDSWITH "-iphonesimulator.dylib"' + ' OR senderImagePath ENDSWITH "_ctypes.framework/_ctypes"' + ' OR senderImagePath ENDSWITH "/Python"' + " )" + ")", ], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, @@ -1334,10 +1355,13 @@ def test_run_app_simulator_non_integer_pid(run_command, first_app_config, tmp_pa "compact", "--predicate", 'senderImagePath ENDSWITH "/First App"' - ' OR (processImagePath ENDSWITH "/First App"' - ' AND (senderImagePath ENDSWITH "-iphonesimulator.so"' - ' OR senderImagePath ENDSWITH "-iphonesimulator.dylib"' - ' OR senderImagePath ENDSWITH "_ctypes.framework/_ctypes"))', + 'OR (processImagePath ENDSWITH "/First App"' + ' AND (senderImagePath ENDSWITH "-iphonesimulator.so"' + ' OR senderImagePath ENDSWITH "-iphonesimulator.dylib"' + ' OR senderImagePath ENDSWITH "_ctypes.framework/_ctypes"' + ' OR senderImagePath ENDSWITH "/Python"' + " )" + ")", ], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, @@ -1438,10 +1462,13 @@ def test_run_app_test_mode(run_command, first_app_config, tmp_path): "compact", "--predicate", 'senderImagePath ENDSWITH "/First App"' - ' OR (processImagePath ENDSWITH "/First App"' - ' AND (senderImagePath ENDSWITH "-iphonesimulator.so"' - ' OR senderImagePath ENDSWITH "-iphonesimulator.dylib"' - ' OR senderImagePath ENDSWITH "_ctypes.framework/_ctypes"))', + 'OR (processImagePath ENDSWITH "/First App"' + ' AND (senderImagePath ENDSWITH "-iphonesimulator.so"' + ' OR senderImagePath ENDSWITH "-iphonesimulator.dylib"' + ' OR senderImagePath ENDSWITH "_ctypes.framework/_ctypes"' + ' OR senderImagePath ENDSWITH "/Python"' + " )" + ")", ], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, @@ -1554,10 +1581,13 @@ def test_run_app_test_mode_with_passthrough(run_command, first_app_config, tmp_p "compact", "--predicate", 'senderImagePath ENDSWITH "/First App"' - ' OR (processImagePath ENDSWITH "/First App"' - ' AND (senderImagePath ENDSWITH "-iphonesimulator.so"' - ' OR senderImagePath ENDSWITH "-iphonesimulator.dylib"' - ' OR senderImagePath ENDSWITH "_ctypes.framework/_ctypes"))', + 'OR (processImagePath ENDSWITH "/First App"' + ' AND (senderImagePath ENDSWITH "-iphonesimulator.so"' + ' OR senderImagePath ENDSWITH "-iphonesimulator.dylib"' + ' OR senderImagePath ENDSWITH "_ctypes.framework/_ctypes"' + ' OR senderImagePath ENDSWITH "/Python"' + " )" + ")", ], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, @@ -1715,10 +1745,13 @@ def test_run_app_debugger(run_command, first_app_generated, tmp_path, dummy_debu "compact", "--predicate", 'senderImagePath ENDSWITH "/First App"' - ' OR (processImagePath ENDSWITH "/First App"' - ' AND (senderImagePath ENDSWITH "-iphonesimulator.so"' - ' OR senderImagePath ENDSWITH "-iphonesimulator.dylib"' - ' OR senderImagePath ENDSWITH "_ctypes.framework/_ctypes"))', + 'OR (processImagePath ENDSWITH "/First App"' + ' AND (senderImagePath ENDSWITH "-iphonesimulator.so"' + ' OR senderImagePath ENDSWITH "-iphonesimulator.dylib"' + ' OR senderImagePath ENDSWITH "_ctypes.framework/_ctypes"' + ' OR senderImagePath ENDSWITH "/Python"' + " )" + ")", ], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, diff --git a/tests/platforms/macOS/app/test_run.py b/tests/platforms/macOS/app/test_run.py index 872bd24064..086b413b35 100644 --- a/tests/platforms/macOS/app/test_run.py +++ b/tests/platforms/macOS/app/test_run.py @@ -58,9 +58,14 @@ def test_run_gui_app(run_command, first_app_config, sleep_zero, tmp_path, monkey "--style", "compact", "--predicate", - f'senderImagePath=="{sender}"' - f' OR (processImagePath=="{sender}"' - ' AND senderImagePath=="/usr/lib/libffi.dylib")', + ( + f'senderImagePath=="{sender}"' + f' OR (processImagePath=="{sender}"' + ' AND (senderImagePath=="/usr/lib/libffi.dylib" ' + ' OR senderImagePath ENDSWITH "/Python" ' + ' OR senderImagePath ENDSWITH ".abi3.so")' + " )" + ), ], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, @@ -121,9 +126,14 @@ def test_run_gui_app_with_passthrough( "--style", "compact", "--predicate", - f'senderImagePath=="{sender}"' - f' OR (processImagePath=="{sender}"' - ' AND senderImagePath=="/usr/lib/libffi.dylib")', + ( + f'senderImagePath=="{sender}"' + f' OR (processImagePath=="{sender}"' + ' AND (senderImagePath=="/usr/lib/libffi.dylib" ' + ' OR senderImagePath ENDSWITH "/Python" ' + ' OR senderImagePath ENDSWITH ".abi3.so")' + " )" + ), ], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, @@ -171,9 +181,14 @@ def test_run_gui_app_failed(run_command, first_app_config, sleep_zero, tmp_path) "--style", "compact", "--predicate", - f'senderImagePath=="{sender}"' - f' OR (processImagePath=="{sender}"' - ' AND senderImagePath=="/usr/lib/libffi.dylib")', + ( + f'senderImagePath=="{sender}"' + f' OR (processImagePath=="{sender}"' + ' AND (senderImagePath=="/usr/lib/libffi.dylib" ' + ' OR senderImagePath ENDSWITH "/Python" ' + ' OR senderImagePath ENDSWITH ".abi3.so")' + " )" + ), ], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, @@ -218,9 +233,14 @@ def test_run_gui_app_find_pid_failed( "--style", "compact", "--predicate", - f'senderImagePath=="{sender}"' - f' OR (processImagePath=="{sender}"' - ' AND senderImagePath=="/usr/lib/libffi.dylib")', + ( + f'senderImagePath=="{sender}"' + f' OR (processImagePath=="{sender}"' + ' AND (senderImagePath=="/usr/lib/libffi.dylib" ' + ' OR senderImagePath ENDSWITH "/Python" ' + ' OR senderImagePath ENDSWITH ".abi3.so")' + " )" + ), ], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, @@ -271,9 +291,14 @@ def test_run_gui_app_test_mode( "--style", "compact", "--predicate", - f'senderImagePath=="{sender}"' - f' OR (processImagePath=="{sender}"' - ' AND senderImagePath=="/usr/lib/libffi.dylib")', + ( + f'senderImagePath=="{sender}"' + f' OR (processImagePath=="{sender}"' + ' AND (senderImagePath=="/usr/lib/libffi.dylib" ' + ' OR senderImagePath ENDSWITH "/Python" ' + ' OR senderImagePath ENDSWITH ".abi3.so")' + " )" + ), ], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, @@ -334,9 +359,14 @@ def test_run_gui_app_debugger( "--style", "compact", "--predicate", - f'senderImagePath=="{sender}"' - f' OR (processImagePath=="{sender}"' - ' AND senderImagePath=="/usr/lib/libffi.dylib")', + ( + f'senderImagePath=="{sender}"' + f' OR (processImagePath=="{sender}"' + ' AND (senderImagePath=="/usr/lib/libffi.dylib" ' + ' OR senderImagePath ENDSWITH "/Python" ' + ' OR senderImagePath ENDSWITH ".abi3.so")' + " )" + ), ], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, diff --git a/tests/platforms/macOS/test_macOS_log_clean_filter.py b/tests/platforms/macOS/test_macOS_log_clean_filter.py index 2b5fc415a1..9a047910e0 100644 --- a/tests/platforms/macOS/test_macOS_log_clean_filter.py +++ b/tests/platforms/macOS/test_macOS_log_clean_filter.py @@ -44,16 +44,36 @@ "2022-11-14 13:21:14.972 Df My App[59972:780a15] ", ("", False), ), - # macOS App log + # macOS App log (std-nslog 1.*) ( "2022-11-14 13:21:15.341 Df My App[59972:780a15] (libffi.dylib) Hello World!", ("Hello World!", True), ), - # Empty macOS App log + # Empty macOS App log (std-nslog 1.*) ( "2022-11-14 13:21:15.341 Df My App[59972:780a15] (libffi.dylib) ", ("", True), ), + # macOS App log (os_log shim) + ( + "2022-11-14 13:21:15.341 Df My App[59972:780a15] (_oslog_shim.abi3.so) Hello World!", + ("Hello World!", True), + ), + # Empty macOS App log (os_log shim) + ( + "2022-11-14 13:21:15.341 Df My App[59972:780a15] (_oslog_shim.abi3.so) ", + ("", True), + ), + # macOS App log (CPython use_system_logger) + ( + "2022-11-14 13:21:15.341 Df My App[59972:780a15] (Python) Hello World!", + ("Hello World!", True), + ), + # Empty macOS App log (CPython use_system_logger) + ( + "2022-11-14 13:21:15.341 Df My App[59972:780a15] (Python) ", + ("", True), + ), # iOS App log (old style .so libraries) ( "2022-11-14 13:21:15.341 Df My App[59972:780a15] (_ctypes.cpython-312-iphonesimulator.so) Hello World!", diff --git a/tests/platforms/macOS/xcode/test_run.py b/tests/platforms/macOS/xcode/test_run.py index 58084a8782..3a762fd885 100644 --- a/tests/platforms/macOS/xcode/test_run.py +++ b/tests/platforms/macOS/xcode/test_run.py @@ -57,9 +57,14 @@ def test_run_app(run_command, first_app_config, sleep_zero, tmp_path, monkeypatc "--style", "compact", "--predicate", - f'senderImagePath=="{sender}"' - f' OR (processImagePath=="{sender}"' - ' AND senderImagePath=="/usr/lib/libffi.dylib")', + ( + f'senderImagePath=="{sender}"' + f' OR (processImagePath=="{sender}"' + ' AND (senderImagePath=="/usr/lib/libffi.dylib" ' + ' OR senderImagePath ENDSWITH "/Python" ' + ' OR senderImagePath ENDSWITH ".abi3.so")' + " )" + ), ], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, @@ -118,9 +123,14 @@ def test_run_app_with_passthrough( "--style", "compact", "--predicate", - f'senderImagePath=="{sender}"' - f' OR (processImagePath=="{sender}"' - ' AND senderImagePath=="/usr/lib/libffi.dylib")', + ( + f'senderImagePath=="{sender}"' + f' OR (processImagePath=="{sender}"' + ' AND (senderImagePath=="/usr/lib/libffi.dylib" ' + ' OR senderImagePath ENDSWITH "/Python" ' + ' OR senderImagePath ENDSWITH ".abi3.so")' + " )" + ), ], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, @@ -177,9 +187,14 @@ def test_run_app_test_mode( "--style", "compact", "--predicate", - f'senderImagePath=="{sender}"' - f' OR (processImagePath=="{sender}"' - ' AND senderImagePath=="/usr/lib/libffi.dylib")', + ( + f'senderImagePath=="{sender}"' + f' OR (processImagePath=="{sender}"' + ' AND (senderImagePath=="/usr/lib/libffi.dylib" ' + ' OR senderImagePath ENDSWITH "/Python" ' + ' OR senderImagePath ENDSWITH ".abi3.so")' + " )" + ), ], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, @@ -241,9 +256,14 @@ def test_run_app_test_mode_with_passthrough( "--style", "compact", "--predicate", - f'senderImagePath=="{sender}"' - f' OR (processImagePath=="{sender}"' - ' AND senderImagePath=="/usr/lib/libffi.dylib")', + ( + f'senderImagePath=="{sender}"' + f' OR (processImagePath=="{sender}"' + ' AND (senderImagePath=="/usr/lib/libffi.dylib" ' + ' OR senderImagePath ENDSWITH "/Python" ' + ' OR senderImagePath ENDSWITH ".abi3.so")' + " )" + ), ], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, From 137d4479630e551d03c62d89f1f60a14dff6b094 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 12 Jun 2026 16:07:15 +0800 Subject: [PATCH 2/5] Add changenote. --- changes/2877.bugfix.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/2877.bugfix.md diff --git a/changes/2877.bugfix.md b/changes/2877.bugfix.md new file mode 100644 index 0000000000..e7eec466b1 --- /dev/null +++ b/changes/2877.bugfix.md @@ -0,0 +1 @@ +Briefcase is now able to display application logs when running apps under macOS 26. If you are experiencing this problem, you need to either (a) upgrade `std-nslog` to 2.0.0+, or target your application at Python 3.14 (or newer). From 047ba3bb73f788984f86dc69fd5ae75b2f881a60 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 12 Jun 2026 18:36:09 +0800 Subject: [PATCH 3/5] Update bootstraps for non-Toga GUI tools. --- src/briefcase/bootstraps/pygame.py | 2 +- src/briefcase/bootstraps/pyside6.py | 2 +- tests/commands/new/test_build_gui_context.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/briefcase/bootstraps/pygame.py b/src/briefcase/bootstraps/pygame.py index 8065a5db4e..4cd304682a 100644 --- a/src/briefcase/bootstraps/pygame.py +++ b/src/briefcase/bootstraps/pygame.py @@ -67,7 +67,7 @@ def pyproject_table_macOS(self): return """\ universal_build = true requires = [ - "std-nslog~=1.0.3", + "std-nslog~=2.0.0", ] """ diff --git a/src/briefcase/bootstraps/pyside6.py b/src/briefcase/bootstraps/pyside6.py index e6abade8b3..15f7dfa1ec 100644 --- a/src/briefcase/bootstraps/pyside6.py +++ b/src/briefcase/bootstraps/pyside6.py @@ -63,7 +63,7 @@ def pyproject_table_macOS(self): # Pyside 6.10 (required for Python 3.14 support) enforces a macOS 13 minimum. min_os_version = "13.0" requires = [ - "std-nslog~=1.0.3", + "std-nslog~=2.0.0", ] """ diff --git a/tests/commands/new/test_build_gui_context.py b/tests/commands/new/test_build_gui_context.py index b9a34c3806..05b7604891 100644 --- a/tests/commands/new/test_build_gui_context.py +++ b/tests/commands/new/test_build_gui_context.py @@ -413,7 +413,7 @@ def main(): # Pyside 6.10 (required for Python 3.14 support) enforces a macOS 13 minimum. min_os_version = "13.0" requires = [ - "std-nslog~=1.0.3", + "std-nslog~=2.0.0", ] """, "pyproject_table_linux": """\ @@ -566,7 +566,7 @@ def main(): "pyproject_table_macOS": """\ universal_build = true requires = [ - "std-nslog~=1.0.3", + "std-nslog~=2.0.0", ] """, "pyproject_table_linux": """\ From 1b28657f046d74119e95ed97545915a0350df77d Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 12 Jun 2026 23:04:20 +0800 Subject: [PATCH 4/5] Bump the std-nslog version in the pyside automation. --- automation/src/automation/bootstraps/pyside6.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/automation/src/automation/bootstraps/pyside6.py b/automation/src/automation/bootstraps/pyside6.py index fa934e2444..a7ba19620f 100644 --- a/automation/src/automation/bootstraps/pyside6.py +++ b/automation/src/automation/bootstraps/pyside6.py @@ -61,6 +61,6 @@ def pyproject_table_macOS(self): min_os_version = "13.0" requires = [ "PySide6-Addons~=6.8", - "std-nslog~=1.0.3", + "std-nslog~=2.0.0", ] """ From af6e6675ffc0045ad8e5750d3bd4e1f71463fd11 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sun, 14 Jun 2026 14:37:36 +0800 Subject: [PATCH 5/5] Corrected a .specify exclusion. --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 56daf881d6..384c1b9a16 100644 --- a/.gitignore +++ b/.gitignore @@ -50,7 +50,7 @@ logs/ .specify/templates .specify/extensions.yml .specify/extensions -.specify/ +.specify/feature.json .specify/init-options.json .specify/integration.json .specify/integrations