diff --git a/.github/workflows/update-binary.yml b/.github/workflows/update-binary.yml index 836d604..1cd895d 100644 --- a/.github/workflows/update-binary.yml +++ b/.github/workflows/update-binary.yml @@ -14,6 +14,7 @@ jobs: fail-fast: false matrix: python-version: [ "3.10", "3.11", "3.12", "3.13", "3.14" ] + framework: [true, false] outputs: BUILD_NUMBER: ${{ steps.build-vars.outputs.BUILD_NUMBER }} @@ -25,9 +26,11 @@ jobs: run: | export BUILD_NUMBER=$(basename $TAG_NAME) export PYTHON_TAG=$(python -c "print('${{ matrix.python-version }}'.split('-')[0])") + export STUB_PREFIX=${{ !matrix.framework && 'L' || '' }} echo "PYTHON_TAG=${PYTHON_TAG}" | tee -a $GITHUB_ENV echo "BUILD_NUMBER=${BUILD_NUMBER}" | tee -a $GITHUB_ENV + echo "STUB_PREFIX=${STUB_PREFIX}" | tee -a $GITHUB_ENV # Expose BUILD_NUMBER as a step output so it can be consumed as a job output echo "BUILD_NUMBER=${BUILD_NUMBER}" | tee -a $GITHUB_OUTPUT @@ -43,26 +46,47 @@ jobs: python-version: ${{ matrix.python-version }} allow-prereleases: true + - name: Setup Miniconda + if: ${{ !matrix.framework }} + uses: conda-incubator/setup-miniconda@8ee1f361103df19b6f8c8655fd3967a8ecb162d5 # v4.0.1 + with: + python-version: ${{ matrix.python-version }} + auto-update-conda: true + activate-environment: briefcase-env + - name: Install Dependencies run: | python -m pip install --upgrade pip python -m pip install git+https://github.com/beeware/briefcase.git - - name: Generate Xcode App Template + - name: Create Framework stub app + if: ${{ matrix.framework }} + run: | + # Generate the stub app + cd stub + briefcase create macOS Xcode + + - name: Create Non-framework stub app + if: ${{ !matrix.framework }} run: | # Generate the stub app + cd stub + briefcase create macOS Xcode -C env_manager=\"conda\" -C universal_build=false + + - name: Build Stub App + run: | cd stub briefcase build macOS Xcode - echo "Build ${PYTHON_TAG}-${BUILD_NUMBER} console stub artefact" + echo "Build ${PYTHON_TAG}-${BUILD_NUMBER} ${{ !matrix.framework && 'non-framework' || 'framework' }} console stub artefact" mv "./build/console-stub/macos/xcode/build/Release/Console Stub.app/Contents/MacOS/Console Stub" Stub codesign --remove-signature Stub - zip Console-Stub-${PYTHON_TAG}-${BUILD_NUMBER}.zip Stub + zip Console-${STUB_PREFIX}Stub-${PYTHON_TAG}-${BUILD_NUMBER}.zip Stub - echo "Build ${PYTHON_TAG}-${BUILD_NUMBER} GUI stub artefact" + echo "Build ${PYTHON_TAG}-${BUILD_NUMBER} ${{ !matrix.framework && 'non-framework' || 'framework' }} GUI stub artefact" mv "./build/gui-stub/macos/xcode/build/Release/GUI Stub.app/Contents/MacOS/GUI Stub" Stub codesign --remove-signature Stub - zip GUI-Stub-${PYTHON_TAG}-${BUILD_NUMBER}.zip Stub + zip GUI-${STUB_PREFIX}Stub-${PYTHON_TAG}-${BUILD_NUMBER}.zip Stub echo "Stub binaries:" ls -1 *.zip @@ -70,7 +94,7 @@ jobs: - name: Upload build artefacts uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: ${{ env.PYTHON_TAG }}-stubs + name: ${{ env.PYTHON_TAG }}-${{ env.STUB_PREFIX }}Stubs path: stub/*.zip - name: Upload Release Asset to S3 @@ -82,8 +106,8 @@ jobs: python -m pip install -U setuptools python -m pip install awscli - aws s3 cp stub/Console-Stub-${PYTHON_TAG}-${BUILD_NUMBER}.zip s3://briefcase-support/python/${PYTHON_TAG}/macOS/Console-Stub-${PYTHON_TAG}-${BUILD_NUMBER}.zip - aws s3 cp stub/GUI-Stub-${PYTHON_TAG}-${BUILD_NUMBER}.zip s3://briefcase-support/python/${PYTHON_TAG}/macOS/GUI-Stub-${PYTHON_TAG}-${BUILD_NUMBER}.zip + aws s3 cp stub/Console-${STUB_PREFIX}Stub-${PYTHON_TAG}-${BUILD_NUMBER}.zip s3://briefcase-support/python/${PYTHON_TAG}/macOS/Console-${STUB_PREFIX}Stub-${PYTHON_TAG}-${BUILD_NUMBER}.zip + aws s3 cp stub/GUI-${STUB_PREFIX}Stub-${PYTHON_TAG}-${BUILD_NUMBER}.zip s3://briefcase-support/python/${PYTHON_TAG}/macOS/GUI-${STUB_PREFIX}Stub-${PYTHON_TAG}-${BUILD_NUMBER}.zip make-release: name: Make Release diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7718a48..7fbcd4d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,3 +8,7 @@ repos: - id: check-case-conflict - id: end-of-file-fixer - id: trailing-whitespace + - repo: https://github.com/zizmorcore/zizmor-pre-commit + rev: v1.25.2 + hooks: + - id: zizmor diff --git a/cookiecutter.json b/cookiecutter.json index 6831353..b978087 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -16,6 +16,7 @@ "universal_build": true, "host_arch": "arm64", "min_os_version": "11.0", + "use_framework": true, "_extensions": [ "briefcase.integrations.cookiecutter.PythonVersionExtension", "briefcase.integrations.cookiecutter.PListExtension" diff --git a/stub/pyproject.toml b/stub/pyproject.toml index 2ea0289..af1f818 100644 --- a/stub/pyproject.toml +++ b/stub/pyproject.toml @@ -10,11 +10,11 @@ template = ".." [tool.briefcase.app.gui-stub] formal_name = "GUI Stub" -description = "A stub binary for GUI apps that can be integrated into the macOS app template" +description = "A stub binary for macOS GUI apps" sources = ['src/gui_stub'] [tool.briefcase.app.console-stub] formal_name = "Console Stub" -description = "A stub binary for console apps that can be integrated into the macOS app template" +description = "A stub binary for macOS console apps" sources = ['src/console_stub'] console_app = true diff --git a/{{ cookiecutter.format }}/{{ cookiecutter.class_name }}/main.m b/{{ cookiecutter.format }}/{{ cookiecutter.class_name }}/main.m index 7120d66..79301c2 100644 --- a/{{ cookiecutter.format }}/{{ cookiecutter.class_name }}/main.m +++ b/{{ cookiecutter.format }}/{{ cookiecutter.class_name }}/main.m @@ -5,7 +5,11 @@ #import #import #import +{%- if cookiecutter.use_framework %} #import +{%- else %} +#import +{%- endif %} #include #include #include @@ -96,7 +100,11 @@ int main(int argc, char *argv[]) { // Set the home for the Python interpreter python_tag = @"{{ cookiecutter.python_version|py_tag }}"; +{%- if cookiecutter.use_framework %} python_home = [NSString stringWithFormat:@"%@/Python.framework/Versions/%@", frameworksPath, python_tag, nil]; +{%- else %} + python_home = [NSString stringWithFormat:@"%@/python/", resourcePath, nil]; +{%- endif %} debug_log(@"PythonHome: %@", python_home); wtmp_str = Py_DecodeLocale([python_home UTF8String], NULL); status = PyConfig_SetString(&config, &config.home, wtmp_str); diff --git a/{{ cookiecutter.format }}/{{ cookiecutter.formal_name }}.xcodeproj/project.pbxproj b/{{ cookiecutter.format }}/{{ cookiecutter.formal_name }}.xcodeproj/project.pbxproj index 7e39d34..1bbea80 100644 --- a/{{ cookiecutter.format }}/{{ cookiecutter.formal_name }}.xcodeproj/project.pbxproj +++ b/{{ cookiecutter.format }}/{{ cookiecutter.formal_name }}.xcodeproj/project.pbxproj @@ -14,8 +14,12 @@ 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 */; }; +{%- if cookiecutter.use_framework %} 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, ); }; }; +{%- else %} + AA0000022AF0B40500C04AE0 /* libpython{{ cookiecutter.python_version|py_tag }}.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = AA0000012AF0B14D00C04AE0 /* libpython{{ cookiecutter.python_version|py_tag }}.dylib */; }; +{%- endif %} /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -29,6 +33,7 @@ name = "Embed Foundation Extensions"; runOnlyForDeploymentPostprocessing = 0; }; +{%- if cookiecutter.use_framework %} 6060E7742AF0B40500C04AE0 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -40,6 +45,7 @@ name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; +{%- endif %} /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ @@ -53,7 +59,11 @@ 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; }; +{%- if cookiecutter.use_framework %} 6060E76F2AF0B14D00C04AE0 /* Python.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Python.xcframework; sourceTree = ""; }; +{%- else %} + AA0000012AF0B14D00C04AE0 /* libpython{{ cookiecutter.python_version|py_tag }}.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libpython{{ cookiecutter.python_version|py_tag }}.dylib; path = Python/lib/libpython{{ cookiecutter.python_version|py_tag }}.dylib; sourceTree = ""; }; +{%- endif %} /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -64,7 +74,11 @@ 0D354FE62551C1E1009178D1 /* AppKit.framework in Frameworks */, 0D354FEF2551C249009178D1 /* Cocoa.framework in Frameworks */, 0D7B44A82555E01500CBC44B /* Foundation.framework in Frameworks */, +{%- if cookiecutter.use_framework %} 6060E7722AF0B40500C04AE0 /* Python.xcframework in Frameworks */, +{%- else %} + AA0000022AF0B40500C04AE0 /* libpython{{ cookiecutter.python_version|py_tag }}.dylib in Frameworks */, +{%- endif %} ); runOnlyForDeploymentPostprocessing = 0; }; @@ -115,7 +129,11 @@ 60A04BBB28AF5E1000DAA9E5 /* Support */ = { isa = PBXGroup; children = ( +{%- if cookiecutter.use_framework %} 6060E76F2AF0B14D00C04AE0 /* Python.xcframework */, +{%- else %} + AA0000012AF0B14D00C04AE0 /* libpython{{ cookiecutter.python_version|py_tag }}.dylib */, +{%- endif %} ); path = Support; sourceTree = ""; @@ -131,7 +149,11 @@ 0D354FC52551BFBA009178D1 /* Frameworks */, 0D354FC62551BFBA009178D1 /* Resources */, 0D7B44EB2556C8B800CBC44B /* Embed Foundation Extensions */, +{%- if cookiecutter.use_framework %} 6060E7742AF0B40500C04AE0 /* Embed Frameworks */, +{%- else %} + AA0000032AF0B40500C04AE0 /* Embed Python lib */, +{%- endif %} 60A04BC128AF640400DAA9E5 /* Sign Python Binary Modules */, ); buildRules = ( @@ -150,7 +172,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; - LastUpgradeCheck = 1540; + LastUpgradeCheck = 2660; ORGANIZATIONNAME = "{{ cookiecutter.author }}"; TargetAttributes = { 0D354FC72551BFBA009178D1 = { @@ -190,6 +212,27 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ +{%- if not cookiecutter.use_framework %} + AA0000032AF0B40500C04AE0 /* Embed Python lib */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Embed Python lib"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "set -e\nDEST=\"$BUILT_PRODUCTS_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH/python\"\necho \"Embedding Python lib into $DEST\"\nmkdir -p \"$DEST\"\n# Copy the entire lib/ tree, preserving symlinks. Exclude static archives\n# and pkgconfig metadata that are not needed at runtime.\nrsync -a --delete --copy-unsafe-links \\\n --exclude='*.a' \\\n --exclude='pkgconfig' \\\n \"$PROJECT_DIR/Support/Python/lib/\" \"$DEST/lib/\"\n"; + }; +{%- endif %} 60A04BC128AF640400DAA9E5 /* Sign Python Binary Modules */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -286,6 +329,7 @@ ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "{{ cookiecutter.formal_name }}"; SDKROOT = macosx; + STRING_CATALOG_GENERATE_SYMBOLS = YES; }; name = Debug; }; @@ -345,6 +389,7 @@ MTL_FAST_MATH = YES; PRODUCT_NAME = "{{ cookiecutter.formal_name }}"; SDKROOT = macosx; + STRING_CATALOG_GENERATE_SYMBOLS = YES; }; name = Release; }; @@ -363,17 +408,37 @@ COMBINE_HIDPI_IMAGES = YES; DEAD_CODE_STRIPPING = YES; ENABLE_HARDENED_RUNTIME = YES; +{%- if cookiecutter.use_framework %} FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "\"$(PROJECT_DIR)/Support\"", ); +{%- else %} + FRAMEWORK_SEARCH_PATHS = "$(inherited)"; +{%- endif %} GCC_C_LANGUAGE_STANDARD = gnu99; +{%- if cookiecutter.use_framework %} HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\""; +{%- else %} + HEADER_SEARCH_PATHS = "\"$(PROJECT_DIR)/Support/Python/include/python{{ cookiecutter.python_version|py_tag }}\""; +{%- endif %} INFOPLIST_FILE = "{{ cookiecutter.class_name }}/Info.plist"; +{%- if cookiecutter.use_framework %} LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); +{%- else %} + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@executable_path/../Resources/python/lib", + ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "\"$(PROJECT_DIR)/Support/Python/lib\"", + ); +{%- endif %} MACOSX_DEPLOYMENT_TARGET = {{ cookiecutter.min_os_version }}; PRODUCT_BUNDLE_IDENTIFIER = "{{ cookiecutter.bundle }}.{{ cookiecutter.app_name }}"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -396,17 +461,37 @@ DEAD_CODE_STRIPPING = YES; ENABLE_HARDENED_RUNTIME = YES; ENABLE_TESTABILITY = YES; +{%- if cookiecutter.use_framework %} FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "\"$(PROJECT_DIR)/Support\"", ); +{%- else %} + FRAMEWORK_SEARCH_PATHS = "$(inherited)"; +{%- endif %} GCC_C_LANGUAGE_STANDARD = gnu99; +{%- if cookiecutter.use_framework %} HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\""; +{%- else %} + HEADER_SEARCH_PATHS = "\"$(PROJECT_DIR)/Support/Python/include/python{{ cookiecutter.python_version|py_tag }}\""; +{%- endif %} INFOPLIST_FILE = "{{ cookiecutter.class_name }}/Info.plist"; +{%- if cookiecutter.use_framework %} LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); +{%- else %} + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@executable_path/../Resources/python/lib", + ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "\"$(PROJECT_DIR)/Support/Python/lib\"", + ); +{%- endif %} MACOSX_DEPLOYMENT_TARGET = {{ cookiecutter.min_os_version }}; PRODUCT_BUNDLE_IDENTIFIER = "{{ cookiecutter.bundle }}.{{ cookiecutter.app_name }}"; };