From ca45499513fbf0d342b88117fd56d9650a4b2ee5 Mon Sep 17 00:00:00 2001 From: Abdo Date: Thu, 1 Oct 2026 23:10:32 +0300 Subject: [PATCH 1/2] Support subprocesses in spawn mode --- cookiecutter.json | 1 + {{ cookiecutter.format }}/briefcase.toml | 5 +++++ .../{{ cookiecutter.class_name }}/main.m | 14 ++++++++++++++ 3 files changed, 20 insertions(+) diff --git a/cookiecutter.json b/cookiecutter.json index b978087..226cabe 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -17,6 +17,7 @@ "host_arch": "arm64", "min_os_version": "11.0", "use_framework": true, + "allow_subprocesses": false, "_extensions": [ "briefcase.integrations.cookiecutter.PythonVersionExtension", "briefcase.integrations.cookiecutter.PListExtension" diff --git a/{{ cookiecutter.format }}/briefcase.toml b/{{ cookiecutter.format }}/briefcase.toml index d329c55..f8a17e4 100644 --- a/{{ cookiecutter.format }}/briefcase.toml +++ b/{{ cookiecutter.format }}/briefcase.toml @@ -28,7 +28,12 @@ support_path = "Support" cleanup_paths = [ "Support/Python.xcframework/**/python*/config-*-darwin", "Support/Python.xcframework/**/pkgconfig", + {% if not cookiecutter.allow_subprocesses -%} + "Support/Python.xcframework/**/Python.framework/Versions/3.*/bin", + "Support/Python.xcframework/**/Python.framework/Versions/3.*/Resources/Python.app", + {% endif -%} ] + icon.16 = "{{ cookiecutter.class_name }}/Assets.xcassets/{{ cookiecutter.formal_name }}.appiconset/icon-16.png" icon.32 = "{{ cookiecutter.class_name }}/Assets.xcassets/{{ cookiecutter.formal_name }}.appiconset/icon-32.png" icon.64 = "{{ cookiecutter.class_name }}/Assets.xcassets/{{ cookiecutter.formal_name }}.appiconset/icon-64.png" diff --git a/{{ cookiecutter.format }}/{{ cookiecutter.class_name }}/main.m b/{{ cookiecutter.format }}/{{ cookiecutter.class_name }}/main.m index 41ac8c7..8f7159d 100644 --- a/{{ cookiecutter.format }}/{{ cookiecutter.class_name }}/main.m +++ b/{{ cookiecutter.format }}/{{ cookiecutter.class_name }}/main.m @@ -247,6 +247,20 @@ int main(int argc, char *argv[]) { setup_stdout(mainBundle); {% endif -%} + // Point sys.executable at bin/python3 so that the app can start Python subprocesses. + path = [python_home stringByAppendingPathComponent:@"bin/python3"]; + if ([[NSFileManager defaultManager] isExecutableFileAtPath:path]) { + debug_log(@"Setting sys.executable: %@", path); + module_attr = PyUnicode_FromString([path UTF8String]); + if (module_attr == NULL + || PySys_SetObject("executable", module_attr) < 0 + || PySys_SetObject("_base_executable", module_attr) < 0) { + crash_dialog(@"Could not set sys.executable"); + exit(-16); + } + Py_DECREF(module_attr); + } + // Start the app module. // // From here to Py_ObjectCall(runmodule...) is effectively From 8c5bd32c2f87b33af11084aaa3b067a837dc4b92 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 5 Oct 2026 14:18:47 +0800 Subject: [PATCH 2/2] Use an empty string for sys.executable if subprocesses are not allowed. --- .../{{ cookiecutter.class_name }}/main.m | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/{{ cookiecutter.format }}/{{ cookiecutter.class_name }}/main.m b/{{ cookiecutter.format }}/{{ cookiecutter.class_name }}/main.m index 8f7159d..cc2c7fb 100644 --- a/{{ cookiecutter.format }}/{{ cookiecutter.class_name }}/main.m +++ b/{{ cookiecutter.format }}/{{ cookiecutter.class_name }}/main.m @@ -248,18 +248,24 @@ int main(int argc, char *argv[]) { {% endif -%} // Point sys.executable at bin/python3 so that the app can start Python subprocesses. + // If the executable doesn't exist, clear sys.executable so that subprocesses + // trying to use sys.executable can't fork-bomb the app. path = [python_home stringByAppendingPathComponent:@"bin/python3"]; if ([[NSFileManager defaultManager] isExecutableFileAtPath:path]) { debug_log(@"Setting sys.executable: %@", path); module_attr = PyUnicode_FromString([path UTF8String]); - if (module_attr == NULL - || PySys_SetObject("executable", module_attr) < 0 - || PySys_SetObject("_base_executable", module_attr) < 0) { - crash_dialog(@"Could not set sys.executable"); - exit(-16); - } - Py_DECREF(module_attr); + } else { + debug_log(@"Clearing sys.executable"); + module_attr = PyUnicode_FromString(""); + } + + if (module_attr == NULL + || PySys_SetObject("executable", module_attr) < 0 + || PySys_SetObject("_base_executable", module_attr) < 0) { + crash_dialog(@"Could not set sys.executable"); + exit(-16); } + Py_DECREF(module_attr); // Start the app module. //