Skip to content

Commit

Permalink
Merge pull request #47 from beeware/framework-lib
Browse files Browse the repository at this point in the history
Switch to a dynamic framework.
  • Loading branch information
freakboy3742 authored Aug 15, 2024
2 parents a80c3d3 + 9fb8aae commit b47ad1c
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 45 deletions.
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

0 comments on commit b47ad1c

Please sign in to comment.