diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 34f088d..4173054 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -7,9 +7,13 @@ updates: # Check for updates on the first Sunday of every month, 8PM UTC interval: "cron" cronjob: "0 20 * * sun#1" + cooldown: + default-days: 7 - package-ecosystem: "pre-commit" directory: "/" schedule: # Check for updates on the first Sunday of every month, 8PM UTC interval: "cron" cronjob: "0 20 * * sun#1" + cooldown: + default-days: 7 diff --git a/.github/workflows/check-pr-template.yml b/.github/workflows/check-pr-template.yml index 9196bac..bd2ae75 100644 --- a/.github/workflows/check-pr-template.yml +++ b/.github/workflows/check-pr-template.yml @@ -4,7 +4,9 @@ on: pull_request: types: [opened, edited, reopened, synchronize] +permissions: {} + jobs: check-pr-template: name: Check PR template - uses: beeware/.github/.github/workflows/pr-checklist.yml@main + uses: beeware/.github/.github/workflows/pr-checklist.yml@741cc5a311012ee0ee195938889a337449d01eeb # main diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 48ac277..d848060 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,17 +18,23 @@ defaults: env: FORCE_COLOR: "1" +permissions: {} + jobs: pre-commit: name: Pre-commit checks - uses: beeware/.github/.github/workflows/pre-commit-run.yml@main + uses: beeware/.github/.github/workflows/pre-commit-run.yml@741cc5a311012ee0ee195938889a337449d01eeb # main + permissions: + contents: read with: pre-commit-source: pre-commit verify-apps: name: Build apps needs: pre-commit - uses: beeware/.github/.github/workflows/app-build-verify.yml@main + uses: beeware/.github/.github/workflows/app-build-verify.yml@741cc5a311012ee0ee195938889a337449d01eeb # main + permissions: + contents: read with: python-version: ${{ matrix.python-version }} runner-os: macos-15 diff --git a/.github/workflows/new-issue.yml b/.github/workflows/new-issue.yml index e4a99c0..a5e1214 100644 --- a/.github/workflows/new-issue.yml +++ b/.github/workflows/new-issue.yml @@ -11,8 +11,9 @@ jobs: add-to-project: name: Add issue to BeeWare project runs-on: ubuntu-latest + permissions: {} steps: - - uses: actions/add-to-project@v2.0.0 + - uses: actions/add-to-project@5afcf98fcd03f1c2f92c3c83f58ae24323cc57fd # v2.0.0 with: project-url: https://github.com/orgs/beeware/projects/1 github-token: ${{ secrets.BRUTUS_PAT_TOKEN }} diff --git a/.github/workflows/update-binary.yml b/.github/workflows/update-binary.yml index 7f38f72..7da28f4 100644 --- a/.github/workflows/update-binary.yml +++ b/.github/workflows/update-binary.yml @@ -4,13 +4,17 @@ on: tags: - 'b*' +permissions: {} + jobs: build-stubs: name: Build stub binaries runs-on: macos-26 strategy: + 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 }} @@ -22,47 +26,77 @@ 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 - name: Checkout Template - uses: actions/checkout@v7.0.0 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false - name: Setup Python ${{ matrix.python-version }} - uses: actions/setup-python@v6.3.0 + if: ${{ matrix.framework }} + uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 with: 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 + # TODO: use the WIP branch to build until the feature is finalized. + python -m pip install git+https://github.com/freakboy3742/briefcase.git@alt-env-support + + - name: Create Framework stub app + if: ${{ matrix.framework }} + run: | + # Generate the stub app + cd stub + briefcase create macOS Xcode - - name: Generate Xcode App Template + - 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 ${{ env.PYTHON_TAG }}-${{ env.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-${{ env.PYTHON_TAG }}-${{ env.BUILD_NUMBER }}.zip Stub + zip Console-${STUB_PREFIX}Stub-${PYTHON_TAG}-${BUILD_NUMBER}.zip Stub - echo "Build ${{ env.PYTHON_TAG }}-${{ env.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-${{ env.PYTHON_TAG }}-${{ env.BUILD_NUMBER }}.zip Stub + zip GUI-${STUB_PREFIX}Stub-${PYTHON_TAG}-${BUILD_NUMBER}.zip Stub echo "Stub binaries:" ls -1 *.zip - name: Upload build artefacts - uses: actions/upload-artifact@v7.0.1 + 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 @@ -74,29 +108,29 @@ jobs: python -m pip install -U setuptools python -m pip install awscli - 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 + 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 - runs-on: macOS-latest + runs-on: macos-26 needs: [ build-stubs ] + permissions: + contents: write steps: - name: Get build artifacts - uses: actions/download-artifact@v8.0.1 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: path: dist merge-multiple: true - name: Create Release - uses: ncipollo/release-action@v1.21.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.10-3.14. - - artifacts: "dist/*" + env: + GH_TOKEN: ${{ github.token }} + BUILD_NUMBER: ${{ needs.build-stubs.outputs.BUILD_NUMBER }} + run: | + gh release create "${BUILD_NUMBER}" dist/* \ + --repo "${GITHUB_REPOSITORY}" \ + --title "${BUILD_NUMBER}" \ + --draft \ + --notes "$(printf 'Build %s of the Briefcase macOS stub binary.\n\nIncludes support for Python 3.10-3.14.\n' "${BUILD_NUMBER}")" 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 }}"; };