diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 30a6be2..eab98e8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,5 +38,14 @@ jobs: strategy: fail-fast: false matrix: - python-version: [ "3.8", "3.9", "3.10", "3.11", "3.12" ] + python-version: [ "3.9", "3.10", "3.11", "3.12", "3.13-dev" ] framework: [ "toga", "pyside6", "pygame", "console" ] + + exclude: + # PySide6 hasn't published 3.13 wheels. + - python-version: "3.13-dev" + framework: "pyside6" + + # Pygame hasn't published 3.13 wheels. + - python-version: "3.13-dev" + framework: "pygame" diff --git a/.github/workflows/update-binary.yml b/.github/workflows/update-binary.yml index feb51fb..41e6691 100644 --- a/.github/workflows/update-binary.yml +++ b/.github/workflows/update-binary.yml @@ -33,7 +33,10 @@ jobs: - name: Install Dependencies run: | python -m pip install --upgrade pip - python -m pip install git+https://github.com/beeware/briefcase.git + # TODO - Revert to the development version of Briefcase + # Use the development version of Briefcase + # python -m pip install git+https://github.com/beeware/briefcase.git + python -m pip install git+https://github.com/freakboy3742/briefcase.git@version-bumps - name: Generate Xcode App Template run: | diff --git a/{{ cookiecutter.format }}/briefcase.toml b/{{ cookiecutter.format }}/briefcase.toml index 8c18562..d058152 100644 --- a/{{ cookiecutter.format }}/briefcase.toml +++ b/{{ cookiecutter.format }}/briefcase.toml @@ -1,4 +1,8 @@ # Generated using Python {{ cookiecutter.python_version }} +[briefcase] +# This is the start of the framework-based support package era. +target_version = "0.3.20" + [paths] app_path = "{{ cookiecutter.class_name }}/app" app_packages_path = "{{ cookiecutter.class_name }}/app_packages" @@ -7,13 +11,14 @@ entitlements_path = "{{ cookiecutter.class_name }}/{{ cookiecutter.app_name }}.e support_path = "Support" {{ { - "3.8": "support_revision = 14", - "3.9": "support_revision = 12", - "3.10": "support_revision = 8", - "3.11": "support_revision = 3", - "3.12": "support_revision = 2", + "3.9": "support_revision = 13", + "3.10": "support_revision = 9", + "3.11": "support_revision = 4", + "3.12": "support_revision = 3", + "3.13": "support_revision = 0", }.get(cookiecutter.python_version|py_tag, "") }} - +cleanup_paths = [ +] 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 a250b27..ad55e05 100644 --- a/{{ cookiecutter.format }}/{{ cookiecutter.class_name }}/main.m +++ b/{{ cookiecutter.format }}/{{ cookiecutter.class_name }}/main.m @@ -5,7 +5,7 @@ #import #import #import -#include +#import #include #include #include @@ -32,6 +32,8 @@ int main(int argc, char *argv[]) { PyConfig config; NSBundle *mainBundle; NSString *resourcePath; + NSString *frameworksPath; + NSString *python_tag; NSString *python_home; NSString *app_module_name; NSString *path; @@ -55,6 +57,7 @@ int main(int argc, char *argv[]) { // Set the resource path for the app mainBundle = get_main_bundle(); resourcePath = [mainBundle resourcePath]; + frameworksPath = [mainBundle privateFrameworksPath]; // Generate an isolated Python configuration. debug_log(@"Configuring isolated Python..."); @@ -72,6 +75,8 @@ int main(int argc, char *argv[]) { config.write_bytecode = 0; // Isolated apps need to set the full PYTHONPATH manually. config.module_search_paths_set = 1; + // Enable verbose logging for debug purposes + // config.verbose = 1; debug_log(@"Pre-initializing Python runtime..."); status = Py_PreInitialize(&preconfig); @@ -82,7 +87,8 @@ int main(int argc, char *argv[]) { } // Set the home for the Python interpreter - python_home = [NSString stringWithFormat:@"%@/support/python-stdlib", resourcePath, nil]; + python_tag = @"{{ cookiecutter.python_version|py_tag }}"; + python_home = [NSString stringWithFormat:@"%@/Python.framework/Versions/%@", frameworksPath, python_tag, nil]; debug_log(@"PythonHome: %@", python_home); wtmp_str = Py_DecodeLocale([python_home UTF8String], NULL); status = PyConfig_SetString(&config, &config.home, wtmp_str); @@ -125,20 +131,8 @@ int main(int argc, char *argv[]) { // Set the full module path. This includes the stdlib, site-packages, and app code. debug_log(@"PYTHONPATH:"); - // The .zip form of the stdlib - path = [NSString stringWithFormat:@"%@/support/python{{ cookiecutter.python_version|py_libtag }}.zip", resourcePath, nil]; - debug_log(@"- %@", path); - wtmp_str = Py_DecodeLocale([path UTF8String], NULL); - status = PyWideStringList_Append(&config.module_search_paths, wtmp_str); - if (PyStatus_Exception(status)) { - crash_dialog([NSString stringWithFormat:@"Unable to set .zip form of stdlib path: %s", status.err_msg, nil]); - PyConfig_Clear(&config); - Py_ExitStatusException(status); - } - PyMem_RawFree(wtmp_str); - // The unpacked form of the stdlib - path = [NSString stringWithFormat:@"%@/support/python-stdlib", resourcePath, nil]; + path = [NSString stringWithFormat:@"%@/lib/python%@", python_home, python_tag, nil]; debug_log(@"- %@", path); wtmp_str = Py_DecodeLocale([path UTF8String], NULL); status = PyWideStringList_Append(&config.module_search_paths, wtmp_str); @@ -150,7 +144,7 @@ int main(int argc, char *argv[]) { PyMem_RawFree(wtmp_str); // Add the stdlib binary modules path - path = [NSString stringWithFormat:@"%@/support/python-stdlib/lib-dynload", resourcePath, nil]; + path = [NSString stringWithFormat:@"%@/lib/python%@/lib-dynload", python_home, python_tag, nil]; debug_log(@"- %@", path); wtmp_str = Py_DecodeLocale([path UTF8String], NULL); status = PyWideStringList_Append(&config.module_search_paths, wtmp_str); @@ -401,6 +395,13 @@ void setup_stdout(NSBundle *mainBundle) { int ret = 0; const char *nslog_script; + // If the app is running under Xcode 15 or later, we don't need to do anything, + // as stdout and stderr are automatically captured by the in-IDE console. + // See https://developer.apple.com/forums/thread/705868 for details. + if (getenv("IDE_DISABLED_OS_ACTIVITY_DT_MODE")) { + return; + } + // Install the nslog script to redirect stdout/stderr if available. // Set the name of the python NSLog bootstrap script nslog_script = [ diff --git a/{{ cookiecutter.format }}/{{ cookiecutter.formal_name }}.xcodeproj/project.pbxproj b/{{ cookiecutter.format }}/{{ cookiecutter.formal_name }}.xcodeproj/project.pbxproj index c27b914..8612c16 100644 --- a/{{ cookiecutter.format }}/{{ cookiecutter.formal_name }}.xcodeproj/project.pbxproj +++ b/{{ cookiecutter.format }}/{{ cookiecutter.formal_name }}.xcodeproj/project.pbxproj @@ -14,30 +14,30 @@ 0D354FEF2551C249009178D1 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D354FEE2551C249009178D1 /* Cocoa.framework */; }; 0D7B44A82555E01500CBC44B /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D7B44A72555E01500CBC44B /* Foundation.framework */; }; 0D7B44DA2556C84100CBC44B /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D354FD72551BFBD009178D1 /* main.m */; }; - 60A04BBF28AF5E7400DAA9E5 /* python-stdlib in Copy Python standard library */ = {isa = PBXBuildFile; fileRef = 60A04BBE28AF5E7400DAA9E5 /* python-stdlib */; }; - 60A04BC028AF5EC000DAA9E5 /* Python.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 60A04BBC28AF5E6900DAA9E5 /* Python.xcframework */; }; + 6060E7722AF0B40500C04AE0 /* Python.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6060E76F2AF0B14D00C04AE0 /* Python.xcframework */; }; + 6060E7732AF0B40500C04AE0 /* Python.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 6060E76F2AF0B14D00C04AE0 /* Python.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ - 0D7B44EB2556C8B800CBC44B /* Embed App Extensions */ = { + 0D7B44EB2556C8B800CBC44B /* Embed Foundation Extensions */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 13; files = ( ); - name = "Embed App Extensions"; + name = "Embed Foundation Extensions"; runOnlyForDeploymentPostprocessing = 0; }; - 609384A728C873E2005B2A5D /* Copy Python standard library */ = { + 6060E7742AF0B40500C04AE0 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; - dstPath = support; - dstSubfolderSpec = 7; + dstPath = ""; + dstSubfolderSpec = 10; files = ( - 60A04BBF28AF5E7400DAA9E5 /* python-stdlib in Copy Python standard library */, + 6060E7732AF0B40500C04AE0 /* Python.xcframework in Embed Frameworks */, ); - name = "Copy Python standard library"; + name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ @@ -53,8 +53,7 @@ 0D354FE52551C1E1009178D1 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; 0D354FEE2551C249009178D1 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 0D7B44A72555E01500CBC44B /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; - 60A04BBC28AF5E6900DAA9E5 /* Python.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Python.xcframework; sourceTree = ""; }; - 60A04BBE28AF5E7400DAA9E5 /* python-stdlib */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "python-stdlib"; sourceTree = ""; }; + 6060E76F2AF0B14D00C04AE0 /* Python.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Python.xcframework; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -64,8 +63,8 @@ files = ( 0D354FE62551C1E1009178D1 /* AppKit.framework in Frameworks */, 0D354FEF2551C249009178D1 /* Cocoa.framework in Frameworks */, - 60A04BC028AF5EC000DAA9E5 /* Python.xcframework in Frameworks */, 0D7B44A82555E01500CBC44B /* Foundation.framework in Frameworks */, + 6060E7722AF0B40500C04AE0 /* Python.xcframework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -116,8 +115,7 @@ 60A04BBB28AF5E1000DAA9E5 /* Support */ = { isa = PBXGroup; children = ( - 60A04BBE28AF5E7400DAA9E5 /* python-stdlib */, - 60A04BBC28AF5E6900DAA9E5 /* Python.xcframework */, + 6060E76F2AF0B14D00C04AE0 /* Python.xcframework */, ); path = Support; sourceTree = ""; @@ -132,8 +130,8 @@ 0D354FC42551BFBA009178D1 /* Sources */, 0D354FC52551BFBA009178D1 /* Frameworks */, 0D354FC62551BFBA009178D1 /* Resources */, - 609384A728C873E2005B2A5D /* Copy Python standard library */, - 0D7B44EB2556C8B800CBC44B /* Embed App Extensions */, + 0D7B44EB2556C8B800CBC44B /* Embed Foundation Extensions */, + 6060E7742AF0B40500C04AE0 /* Embed Frameworks */, 60A04BC128AF640400DAA9E5 /* Sign Python Binary Modules */, ); buildRules = ( @@ -152,7 +150,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; - LastUpgradeCheck = 1330; + LastUpgradeCheck = 1540; ORGANIZATIONNAME = "{{ cookiecutter.author }}"; TargetAttributes = { 0D354FC72551BFBA009178D1 = { @@ -209,7 +207,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "set -e\necho \"Signed as $EXPANDED_CODE_SIGN_IDENTITY_NAME ($EXPANDED_CODE_SIGN_IDENTITY)\"\nfind \"$BUILT_PRODUCTS_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH/support/python-stdlib/lib-dynload\" -name \"*.so\" -exec /usr/bin/codesign --force --sign \"$EXPANDED_CODE_SIGN_IDENTITY\" -o runtime --timestamp=none --preserve-metadata=identifier,entitlements,flags --generate-entitlement-der {} \\; \nfind \"$BUILT_PRODUCTS_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH/app_packages\" -name \"*.so\" -exec /usr/bin/codesign --force --sign \"$EXPANDED_CODE_SIGN_IDENTITY\" -o runtime --timestamp=none --preserve-metadata=identifier,entitlements,flags --generate-entitlement-der {} \\; \nfind \"$BUILT_PRODUCTS_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH/app\" -name \"*.so\" -exec /usr/bin/codesign --force --sign \"$EXPANDED_CODE_SIGN_IDENTITY\" -o runtime --timestamp=none --preserve-metadata=identifier,entitlements,flags --generate-entitlement-der {} \\; \n"; + shellScript = "set -e\necho \"Signed as $EXPANDED_CODE_SIGN_IDENTITY_NAME ($EXPANDED_CODE_SIGN_IDENTITY)\"\nfind \"$BUILT_PRODUCTS_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH/app_packages\" -name \"*.so\" -exec /usr/bin/codesign --force --sign \"$EXPANDED_CODE_SIGN_IDENTITY\" -o runtime --timestamp=none --preserve-metadata=identifier,entitlements,flags --generate-entitlement-der {} \\; \nfind \"$BUILT_PRODUCTS_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH/app\" -name \"*.so\" -exec /usr/bin/codesign --force --sign \"$EXPANDED_CODE_SIGN_IDENTITY\" -o runtime --timestamp=none --preserve-metadata=identifier,entitlements,flags --generate-entitlement-der {} \\; \n"; }; /* End PBXShellScriptBuildPhase section */ @@ -259,9 +257,11 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "\"$(PROJECT_DIR)\"", @@ -323,9 +323,11 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "\"$(PROJECT_DIR)\"", @@ -354,12 +356,19 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; CODE_SIGN_ENTITLEMENTS = "{{ cookiecutter.class_name }}/{{ cookiecutter.app_name }}.entitlements"; CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + DEAD_CODE_STRIPPING = YES; ENABLE_HARDENED_RUNTIME = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "\"$(PROJECT_DIR)/Support\"", + ); GCC_C_LANGUAGE_STANDARD = gnu99; + HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\""; INFOPLIST_FILE = "{{ cookiecutter.class_name }}/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -368,7 +377,6 @@ MACOSX_DEPLOYMENT_TARGET = 11.0; PRODUCT_BUNDLE_IDENTIFIER = "{{ cookiecutter.bundle }}.{{ cookiecutter.app_name }}"; PROVISIONING_PROFILE_SPECIFIER = ""; - STRIP_INSTALLED_PRODUCT = NO; }; name = Debug; }; @@ -380,13 +388,20 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; CODE_SIGN_ENTITLEMENTS = "{{ cookiecutter.class_name }}/{{ cookiecutter.app_name }}.entitlements"; CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + DEAD_CODE_STRIPPING = YES; ENABLE_HARDENED_RUNTIME = YES; ENABLE_TESTABILITY = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "\"$(PROJECT_DIR)/Support\"", + ); GCC_C_LANGUAGE_STANDARD = gnu99; + HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\""; INFOPLIST_FILE = "{{ cookiecutter.class_name }}/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -394,7 +409,6 @@ ); MACOSX_DEPLOYMENT_TARGET = 11.0; PRODUCT_BUNDLE_IDENTIFIER = "{{ cookiecutter.bundle }}.{{ cookiecutter.app_name }}"; - STRIP_INSTALLED_PRODUCT = NO; }; name = Release; };