Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
38 changes: 36 additions & 2 deletions .github/workflows/update-binary.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,18 @@ jobs:
runs-on: macos-latest
strategy:
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" ]
outputs:
BUILD_NUMBER: ${{ steps.build-vars.outputs.BUILD_NUMBER }}

steps:
- name: Set Build Variables
id: build-vars
env:
TAG_NAME: ${{ github.ref }}
run: |
export BUILD_NUMBER=$(basename $TAG_NAME)
export PYTHON_TAG=$(python -c "print('.'.join('${{ matrix.python-version }}'.split('.')[:2]))")
export PYTHON_TAG=$(python -c "print('${{ matrix.python-version }}'.split('-')[0])")

echo "PYTHON_TAG=${PYTHON_TAG}" | tee -a $GITHUB_ENV
echo "BUILD_NUMBER=${BUILD_NUMBER}" | tee -a $GITHUB_ENV
Expand Down Expand Up @@ -54,6 +58,12 @@ jobs:
echo "Stub binaries:"
ls -1 *.zip

- name: Upload build artefacts
uses: actions/upload-artifact@v4.3.5
with:
name: ${{ env.PYTHON_TAG }}-stubs
path: stub/*.zip

- name: Upload Release Asset to S3
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
Expand All @@ -65,3 +75,27 @@ jobs:

aws s3 cp stub/Console-Stub-${{ env.PYTHON_TAG }}-${{ env.BUILD_NUMBER }}.zip s3://briefcase-support/python/${{ env.PYTHON_TAG }}/macOS/Console-Stub-${{ env.PYTHON_TAG }}-${{ env.BUILD_NUMBER }}.zip
aws s3 cp stub/GUI-Stub-${{ env.PYTHON_TAG }}-${{ env.BUILD_NUMBER }}.zip s3://briefcase-support/python/${{ env.PYTHON_TAG }}/macOS/GUI-Stub-${{ env.PYTHON_TAG }}-${{ env.BUILD_NUMBER }}.zip

make-release:
name: Make Release
runs-on: macOS-latest
needs: [ build-stubs ]
steps:
- name: Get build artifacts
uses: actions/download-artifact@v4.1.8
with:
path: dist
merge-multiple: true

- name: Create Release
uses: ncipollo/release-action@v1.14.0
with:
name: ${{ needs.build-stubs.outputs.BUILD_NUMBER }}
tag: ${{ needs.build-stubs.outputs.BUILD_NUMBER }}
draft: true
body: |
Build ${{ needs.build-stubs.outputs.BUILD_NUMBER }} of the Briefcase macOS stub binary.

Includes support for Python 3.9-3.13.

artifacts: "dist/*"
17 changes: 11 additions & 6 deletions {{ cookiecutter.format }}/briefcase.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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"
Expand Down
33 changes: 17 additions & 16 deletions {{ cookiecutter.format }}/{{ cookiecutter.class_name }}/main.m
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
#import <Cocoa/Cocoa.h>
#include <Python.h>
#import <Python/Python.h>
#include <dlfcn.h>
#include <libgen.h>
#include <mach-o/dyld.h>
Expand All @@ -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;
Expand All @@ -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...");
Expand All @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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 = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand All @@ -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 = "<group>"; };
60A04BBE28AF5E7400DAA9E5 /* python-stdlib */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "python-stdlib"; sourceTree = "<group>"; };
6060E76F2AF0B14D00C04AE0 /* Python.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Python.xcframework; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand All @@ -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;
};
Expand Down Expand Up @@ -116,8 +115,7 @@
60A04BBB28AF5E1000DAA9E5 /* Support */ = {
isa = PBXGroup;
children = (
60A04BBE28AF5E7400DAA9E5 /* python-stdlib */,
60A04BBC28AF5E6900DAA9E5 /* Python.xcframework */,
6060E76F2AF0B14D00C04AE0 /* Python.xcframework */,
);
path = Support;
sourceTree = "<group>";
Expand All @@ -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 = (
Expand All @@ -152,7 +150,7 @@
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1330;
LastUpgradeCheck = 1540;
ORGANIZATIONNAME = "{{ cookiecutter.author }}";
TargetAttributes = {
0D354FC72551BFBA009178D1 = {
Expand Down Expand Up @@ -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 */

Expand Down Expand Up @@ -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)\"",
Expand Down Expand Up @@ -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)\"",
Expand Down Expand Up @@ -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)",
Expand All @@ -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;
};
Expand All @@ -380,21 +388,27 @@
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)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.0;
PRODUCT_BUNDLE_IDENTIFIER = "{{ cookiecutter.bundle }}.{{ cookiecutter.app_name }}";
STRIP_INSTALLED_PRODUCT = NO;
};
name = Release;
};
Expand Down