feat(flet build): macOS code signing, notarization and AppStore support#6702
feat(flet build): macOS code signing, notarization and AppStore support#6702ndonkoHenri wants to merge 28 commits into
flet build): macOS code signing, notarization and AppStore support#6702Conversation
New `--macos-signing-identity`, `--macos-notarize` and `--macos-notary-profile` options, configurable via `[tool.flet.macos.signing]` and `FLET_MACOS_*` environment variables: - Signs every bundled Mach-O inside-out (discovered by magic bytes, not extension) with hardened runtime and secure timestamp; entitlements are applied to the app bundle and standalone helper executables, libraries are signed bare per Apple guidance. Identity is resolved against the keychain up front (name, SHA-1, or substring; deduplicated across keychains) and every failure is per-file and fatal. - Verifies with `codesign --verify --deep --strict` plus a per-binary coverage assertion. - Optionally notarizes (`notarytool submit --wait` with a keychain profile or App Store Connect API key env vars), surfaces Apple's notarization log on rejection, then staples and validates the ticket. - Adds `com.apple.security.cs.allow-unsigned-executable-memory` to the default entitlements: Apple's libffi allocates W+X closure memory on x86_64, breaking ctypes/cffi callbacks under the hardened runtime. - Fixes the entitlements templates emitting self-closing tags with a space (`<true />`), which Xcode tolerates but codesign's AMFI parser rejects; the signer additionally normalizes any entitlements file through plistlib before use. - Documents signing, notarization, credentials, CI setup and troubleshooting in the macOS publish guide, and adds the new environment variables to the reference page (now fully sorted). Ref #2347
Expand every function, method, and dataclass in flet_cli/utils/macos_sign.py, the sign_macos_app/_macos_notary_credentials methods, and the test suite to full Google-style docstrings (Args/Returns/Raises), including the rationale behind the non-obvious decisions: the inside-out signing order and why it protects resource seals, the entitlements policy (app bundle + MH_EXECUTE helpers only), fingerprint-based identity selection, the AMFI plist normalization, and the multi-keychain deduplication. Also refines the macOS publish guide's CI section heading and links the apple-actions/import-codesign-certs action in the troubleshooting table.
`flet build web` and `flet publish` now resolve `FLET_WEB_RENDERER`, `FLET_WEB_ROUTE_URL_STRATEGY` and `FLET_WEB_NO_CDN` from the environment (precedence: CLI flag > pyproject.toml > env var > default), making the `[env: ...]` markers already shown in `--help` accurate. Previously the build ignored them and only the runtime web server read them. Docs: add "env var" example tabs and environment-variables reference links to the Android, macOS and web publish pages; rename the former ".env" tab label to "env var" (flet reads the process environment, it does not auto-load a .env file). Also fix the Android key-alias resolution order, which listed the env var above pyproject.toml though the code resolves pyproject.toml first.
There was a problem hiding this comment.
Hey - I've found 1 security issue
Security issues:
- Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'. (link)
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="sdk/python/packages/flet-cli/src/flet_cli/utils/macos_sign.py" line_range="154-159" />
<code_context>
return subprocess.run(
args,
capture_output=True,
text=True,
timeout=timeout,
)
</code_context>
<issue_to_address>
**security (python.lang.security.audit.dangerous-subprocess-use-audit):** Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.
*Source: opengrep*
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| return subprocess.run( | ||
| args, | ||
| capture_output=True, | ||
| text=True, | ||
| timeout=timeout, | ||
| ) |
There was a problem hiding this comment.
security (python.lang.security.audit.dangerous-subprocess-use-audit): Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.
Source: opengrep
Deploying flet-website-v2 with
|
| Latest commit: |
a934f5c
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://1ab67556.flet-website-v2.pages.dev |
| Branch Preview URL: | https://test-macos-signing.flet-website-v2.pages.dev |
There was a problem hiding this comment.
Pull request overview
Adds macOS code-signing + optional notarization support to flet build macos, including identity resolution, inside-out signing of all Mach-O binaries in the bundle, strict verification/coverage checks, and notarization + stapling via notarytool. The PR also updates templates, docs, and adds a test suite; additionally, web build/publish now honors new env vars for renderer/URL strategy/CDN.
Changes:
- Introduce
flet_cli.utils.macos_signand integrate signing/notarization into the macOS build pipeline (new CLI flags + pyproject/env precedence). - Fix entitlements template boolean formatting (
<true/>/<false/>) and document the new default entitlement for hardened runtime compatibility on Intel Macs. - Extend docs and behavior to include additional environment-variable configuration (macOS signing/notary + web options), plus new unit tests.
Reviewed changes
Copilot reviewed 10 out of 12 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| website/docs/reference/environment-variables.md | Adds/sorts documented env vars (Android signing, storage cache, macOS signing/notary, web options). |
| website/docs/publish/web/static-website/index.md | Documents env-var precedence and adds env-var examples for web route strategy/renderer/CDN. |
| website/docs/publish/macos.md | Adds code signing + notarization guidance, precedence rules, CI/troubleshooting, and notes about default entitlements. |
| website/docs/publish/android.md | Aligns Android signing precedence docs and links env vars to the reference page. |
| sdk/python/templates/build/{{cookiecutter.out_dir}}/macos/Runner/Release.entitlements | Fixes boolean self-closing tag formatting for codesign compatibility. |
| sdk/python/templates/build/{{cookiecutter.out_dir}}/macos/Runner/DebugProfile.entitlements | Same entitlements boolean formatting fix for debug profile. |
| sdk/python/packages/flet-cli/tests/test_macos_sign.py | Adds unit + macOS-only e2e tests for Mach-O detection, identity resolution, entitlements normalization, and ad-hoc signing verification. |
| sdk/python/packages/flet-cli/src/flet_cli/utils/macos_sign.py | New implementation: identity resolution, Mach-O discovery/classification, signing, verification, notarization, stapling. |
| sdk/python/packages/flet-cli/src/flet_cli/commands/publish.py | Adds env-var support for web renderer/route strategy/no-cdn in flet publish web. |
| sdk/python/packages/flet-cli/src/flet_cli/commands/build.py | Invokes macOS signing/notarization after copy_build_output() for flet build macos. |
| sdk/python/packages/flet-cli/src/flet_cli/commands/build_base.py | Adds new macOS signing/notary flags; adds default entitlement; adds env-var support for web renderer/route strategy/no-cdn. |
| CHANGELOG.md | Adds changelog entries describing macOS signing/notarization and entitlements-template fix. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if self.keychain_profile: | ||
| return ["--keychain-profile", self.keychain_profile] | ||
| assert self.api_key and self.api_key_id and self.api_issuer | ||
| return [ | ||
| "--key", | ||
| self.api_key, | ||
| "--key-id", | ||
| self.api_key_id, | ||
| "--issuer", | ||
| self.api_issuer, | ||
| ] |
|
|
||
| ### Bug fixes | ||
|
|
||
| * Fix the generated `macos/Runner/*.entitlements` files being rejected by `codesign` with `AMFIUnserializeXML: syntax error` when used directly for re-signing: the template emitted boolean values as self-closing tags with a space (`<true />`), which Xcode and `plutil` accept but codesign's stricter AMFI plist parser does not. The templates now emit `<true/>`, and `flet build`'s own signing step additionally normalizes any entitlements file through `plistlib` before use, so plist formatting can never break signing by ([#6702](https://github.com/flet-dev/flet/pull/6702)) by @ndonkoHenri. |
| web_renderer=WebRenderer( | ||
| options.web_renderer | ||
| or get_pyproject("tool.flet.web.renderer") | ||
| or os.getenv("FLET_WEB_RENDERER") | ||
| or "canvaskit" | ||
| ), | ||
| route_url_strategy=RouteUrlStrategy( | ||
| options.route_url_strategy | ||
| or get_pyproject("tool.flet.web.route_url_strategy") | ||
| or os.getenv("FLET_WEB_ROUTE_URL_STRATEGY") | ||
| or "path" | ||
| ), |
| @@ -1298,6 +1325,7 @@ def _xml_attr_value(v): | |||
| "web_renderer": ( | |||
| self.options.web_renderer | |||
| or self.get_pyproject("tool.flet.web.renderer") | |||
| or os.getenv("FLET_WEB_RENDERER") | |||
| or "canvaskit" | |||
| ), | |||
…rovenance verification Fixes for three defects found by adversarial review of the signing code, each empirically reproduced with codesign on macOS 15: - Non-canonical frameworks (the only kind pip wheels can ship — the zip format cannot represent the Versions/Current symlink) aborted the whole signing run with 'bundle format unrecognized/ambiguous', or got a junk generic-bundle signature in hybrid layouts like PyQt6's. They are now detected via _is_signable_framework() and their Mach-O files are signed individually — which notarization and library validation accept — while canonical frameworks keep getting sealed as bundles. - Nested helper bundles (.app/.appex/.xpc) were bundle-signed without entitlements, which both stripped the entitlements their main executable had just received (codesign --force discards them without --preserve-metadata) and left hardened-runtime helpers without the JIT/unsigned-memory exceptions Python needs. Their bundle signature now carries the same entitlements as the app. - verify_app()'s per-file safety net used plain 'codesign --verify', which passes for the linker-signed stub every pip wheel ships with — so a file skipped by the signing passes sailed through to notarization rejection or a library-validation kill at runtime. Coverage now also checks provenance via 'codesign --display': linker-signed stubs always fail, real identities must appear in the Authority chain. Also: recognize fat64 (0xcafebabf) universal binaries; anchor the framework main-binary heuristic to the framework root; make a missing Release.entitlements a hard error instead of silently signing without entitlements; document the manual Mac App Store/TestFlight requirements (sandbox entitlement, Apple Distribution cert, provisioning profile, productbuild) the automated pipeline does not cover yet; expand the test suite from 14 to 28 tests (signing order and entitlements routing, all notarization failure paths, provenance verification, non-canonical framework handling).
argparse's default="path" meant options.route_url_strategy was always truthy, so the pyproject (tool.flet.web.route_url_strategy) and FLET_WEB_ROUTE_URL_STRATEGY fallbacks documented for `flet publish` were dead code. The default now lives at the end of the resolution chain, as in `flet build`.
New --macos-app-store, --macos-provisioning-profile and --macos-installer-identity options (pyproject: [tool.flet.macos.signing] app_store / provisioning_profile / installer_identity; env: FLET_MACOS_PROVISIONING_PROFILE / FLET_MACOS_INSTALLER_IDENTITY), verified end-to-end against App Store Connect and TestFlight on 2026-07-22 — the uploaded build installs via TestFlight and loads every bundled native extension under the App Sandbox, closing the scenario of #4543. The store lane differs from Developer ID signing in every dimension that matters, all handled by the new mode: - Apple Distribution identity without the hardened runtime (the store's containment model is the App Sandbox; Apple re-signs on delivery). - Entitlements are the template's with every com.apple.security.cs.* hardened-runtime exception stripped, App Sandbox forced on, and the application-identifier / team-identifier pair injected — derived from the certificate's Team ID and the built app's CFBundleIdentifier. Helper executables and nested helper bundles carry exactly the sandbox-inherit pair. - The provisioning profile is embedded at Contents/embedded.provisionprofile before signing so the app seal covers it, and its App ID is cross-checked against the bundle id up front — a mismatch otherwise surfaces only after upload as ITMS-90889. - The deliverable is a .pkg built with productbuild and signed with the installer certificate — a certificate type invisible under the codesigning keychain policy, so resolve_identity() gained a policy parameter and the installer identity resolves (fail-fast) before any signing work. - LSApplicationCategoryType is validated before signing (App Store validation hard-rejects without it — empirically a 409) and a missing ITSAppUsesNonExemptEncryption produces a warning, both pointing at --info-plist / [tool.flet.macos.info]. - app_store + notarize is rejected: store builds are not notarized. verify_app_store_app() asserts the embedded profile and the sealed application-identifier after signing (the TestFlight ITMS-90889 invariants), build_pkg() verifies the package signature with pkgutil, and the docs' Mac App Store section now covers the automated flow, the one-time portal setup, and the altool validate/upload commands. Unit tests: 28 -> 34. ITMS added to the typos allowlist. The provisioning profile is embedded BEFORE the xattr sweep: profiles are browser downloads carrying com.apple.quarantine, macOS propagates the attribute onto the embedded copy, and App Store Connect processing rejects any quarantined file in the package (error 91109, observed on the playground upload) — a check altool --validate-app does not perform, so it only surfaces after upload.
Fixes and tightening from an adversarial review of the complete signing change set (both distribution lanes), each finding verified before fixing: Correctness: - Prefix-wildcard provisioning profiles (TEAM.com.example.*) were falsely rejected by the profile/bundle-id cross-check, which only accepted exact and full-team wildcards. Wildcard matching now lives in profile_covers_application() with tests. - An installer identity that resolved (under the basic keychain policy) to a non-installer certificate failed only later, deep inside productbuild; it is now rejected up front with the certificate named. - Malformed Info.plist / Release.entitlements in the App Store lane raised raw tracebacks; both now produce clean, actionable errors. - _normalized_entitlements() could silently collide when two entitlements files shared a stem; the normalized copy's name now includes a source-path digest. - verify_app_store_app() misreported a codesign --display failure as an entitlement mismatch; the two cases are now distinct errors. - FLET_WEB_RENDERER / FLET_WEB_ROUTE_URL_STRATEGY env fallbacks bypassed the CLI options' lowercasing; values are now lowered for parity. Organization: - The store entitlements policy moved from the CLI command into macos_sign.app_store_entitlements() (unit-testable), with the helper sandbox-inherit pair as APP_STORE_HELPER_ENTITLEMENTS. - Module docstring now maps both distribution lanes; store-section divider added; duplicated cleanup blocks merged; missing type annotations added; over-explaining docstrings trimmed. Tests (96 -> 107): - --options runtime/--timestamp for real identities is now asserted (the property notarization depends on, previously untested). - New darwin e2e signs an App Store-shaped bundle with real codesign and proves the xattr sweep strips quarantine from the embedded profile (the ASC error-91109 regression) and that the seal covers the profile. - Previously untested error branches covered: missing/invalid entitlements and profile inputs, deep-verification failure, unreadable sealed entitlements, missing main executable, malformed profile plist, staple validation failure; notarize/productbuild happy paths now assert credential forwarding and the /Applications install root. - resolve_identity's default policy pinned; xattr sweep deletion now fails a test; MAS naming unified to App Store; imports and identity fixtures consolidated. Docs: - FLET_MACOS_PROVISIONING_PROFILE and FLET_MACOS_INSTALLER_IDENTITY added to the environment-variables reference (they were undocumented); FLET_MACOS_SIGNING_IDENTITY entry updated for Apple Distribution. - Mac App Store section links options/env vars instead of raw argparse notation; helper-bundle entitlements phrasing corrected; duplicate credentials explanation trimmed; missing bash fence tag added. - Changelog: MAS bullet gains the quarantine/91109 scrub; entries added for the FLET_WEB_* env fallbacks and the flet publish route-url-strategy fix the branch also carries.
The build mode fully determines the certificate type Apple accepts — Developer ID Application for notarized direct distribution, Apple Distribution (or the legacy 3rd Party Mac Developer Application) for the App Store, an installer certificate for the store .pkg — so resolve_identity() now takes the lane's acceptable type prefixes and scopes matching to them: - A partial identity like a bare team ID stays unambiguous per lane even when the keychain holds certificates of several types (previously it matched all of them and errored). - An explicit identity of the wrong type gets a precise error naming the matched certificate and the required type, replacing the store lane's after-the-fact warning and the installer lane's post-resolution name check. - Lanes that require an identity anyway (--macos-notarize, --macos-app-store, and the store installer certificate) auto-discover it when exactly one certificate of the required type exists — the previous hard 'requires an identity' errors become successes in the common single-team case, with the chosen identity logged. Several candidates or none still fail with the candidate list (including the expired-certificate explanation). Discovery never triggers on a plain build: no identity there still means ad-hoc, unchanged. The plain signing lane stays unscoped — Apple Development and corporate certificates are legitimate for local/internal distribution. Tests: 108 -> 115; docs and changelog updated.
Step-by-step map of the resolution algorithm (ad-hoc short-circuit, validity filtering via find-identity -v, fingerprint dedup, type scoping, selection precedence, failure diagnosis order), the rationale for never auto-picking between two valid same-name certificates, and a corrected Returns section (it returns the identity, not its fingerprint). Body comments added for the load-bearing -v semantics and the exact-name- before-substring precedence.
--macos-installer-identity's help still said 'required for App Store builds', stale since auto-discovery; both identity options and the FLET_MACOS_INSTALLER_IDENTITY reference entry now state the when-unset behavior. (--macos-provisioning-profile stays 'required' — profiles are not discoverable.)
The Mac App Store section now matches the page's own per-option convention and the iOS page's depth: portal walkthroughs a newcomer can follow (both distribution certificates with the find-identity -p basic gotcha, explicit App ID registration, Mac App Store Connect profile creation, the App Store Connect app record and where its numeric Apple ID lives), a three-channel configuration example, and dedicated sections with Resolution order for app_store, the provisioning profile, and the installer identity. A new Identity auto-discovery section states the semantics precisely — an identity is 'not configured' only when the CLI option, pyproject key, and environment variable are all unset; resolution always runs first and configured values are never silently replaced — and the notarize section, the signing-identity resolution default, and the CLI help texts (which wrongly implied CLI-only 'unset', and --macos-notarize still claimed to require --macos-signing-identity) now defer to it. The Credentials section documents both notarytool store-credentials flavors — App Store Connect API key (--key/--key-id/--issuer) and Apple ID with app-specific password — including where each credential is created and that the .p8 can be downloaded only once. The credentials resolution order gains its missing terminal Default entry. Proofread adversarially against the implementation: all eight Resolution order lists, the discovery semantics, both credential commands, the altool invocations, and every cross-page anchor verified.
'With --macos-notarize, the signing identity may be omitted' implied the behavior was tied to the CLI flag, but notarization (and App Store mode) can equally be enabled via their pyproject keys — the docs, the env-var reference, and the --macos-signing-identity help now say 'notarizing / App Store builds' instead of naming flags.
The build example and the identity-pinning paragraph referenced the provisioning profile and installer identity before their sections; they now link ahead. The pinning paragraph also links each identity's section instead of enumerating two of its three configuration channels (the env vars were missing) — the sections carry the full resolution order.
'Building for the App Store' now follows the provisioning-profile and installer-identity sections, so every setting the build example uses is defined before it appears — prerequisites → options → build → upload — and the 'detailed below' forward pointer becomes unnecessary.
Systematic sweep of macos.md for config options named in prose without a link (the build-number sentence in Uploading was the trigger — it named only the CLI flag; it now links the Versioning docs). Also linked: the notarize/app-store toggles in the env-var tab notes, the compile flags in the Troubleshooting table, and the APPLE_API_KEY / APPLE_API_KEY_ID / APPLE_API_ISSUER trio, which had no environment-variables reference entries at all and now do. Unlinked-by-convention cases left alone: pyproject keys (no anchor target exists) and --no-* forms adjacent to their linked positive option.
The notarize/app_store boolean pair encoded a four-state lane space
(ad-hoc, sign-only, Developer ID + notarize, App Store) as a 2x2 grid
with an invalid cell that had to be policed by a mutual-exclusion error,
and booleans compose badly across the CLI > pyproject > env layers:
turning one lane on could not turn the other lane's pyproject 'true'
off, so every lane flip needed a paired --no-* negation — and shipping
on both channels from one pyproject was impossible.
The lane is now one value — --macos-distribution {none,developer-id,
app-store} / [tool.flet.macos.signing].distribution — so conflicting
lanes are inexpressible, a CLI value wholesale-replaces the pyproject
value, and one pyproject can hold both channels' settings (they are
naturally lane-scoped) with a single flag flipping between them:
flet build macos --macos-distribution app-store
The resolved value is validated wherever it came from — argparse
choices= only guards the CLI layer, and a pyproject typo must fail
loudly rather than fall through to a silently ad-hoc build.
The signing configuration is also validated BEFORE the build starts
(preflight_macos_signing): identity resolution against the keychain,
notary credentials, the provisioning profile, and the store category
now fail in seconds instead of after the multi-minute Flutter build.
The signing step re-checks everything; the preflight is purely an
early exit and a no-op for unsigned builds.
Both lanes re-verified end-to-end with pyproject-only distribution
config: developer-id (auto-discovered identity, 216 binaries,
notarized, stapled, 7/7 verification) and app-store (both identities
auto-discovered, store entitlements sealed, signed .pkg). Tests
115 -> 126 — the lane dispatch and preflight gain their first
coverage. Docs: new Distribution lanes section with the single
resolution order and a 'Shipping on both channels' recipe; notarize
and App Store sections, CI example, and changelog updated to the
lane vocabulary.
Every signing key except the lane selector itself may now live in a per-lane subtable that overrides the flat key when its lane builds — resolution is CLI option > lane subtable > flat key > environment variable (the lane deliberately beats the flat key, the opposite of iOS's export_methods, where per-lane values silently lose to flat ones). This closes the one real gap auto-discovery leaves in the one-pyproject-two-lanes workflow: keychains where discovery cannot pick (e.g. certificates from several teams) can pin a different identity per lane. For keys only one lane reads (notary_profile, provisioning profile, installer identity) the subtable and flat forms are equivalent, so configs can be grouped by lane for readability. The 'none' lane has no subtable, and a misnamed subtable (e.g. signing.app_store) fails the build instead of being silently ignored — same fail-loud philosophy as the distribution value itself. All five lookup sites route through one macos_signing_setting() helper. Tests 126 -> 130: full precedence chain, per-lane identity pinning flipped by --macos-distribution, the fully lane-organized pyproject, and misnamed-subtable rejection. Docs: the Per-lane settings section (tabbed, with the two-run CLI counterpart — invocations need no lane syntax since an invocation is single-lane), lane steps in the four affected resolution orders, and the dual-lane section renamed to 'Switching lanes'.
route_url_strategy and web_renderer previously lowercased only the CLI option (type=str.lower) and the environment variable, letting a pyproject value like renderer = "CanvasKit" through unlowered. Apply .lower() once to whichever source wins instead; the or-"" guard on the env var is no longer needed since the default keeps the chain non-None. Also reflow an over-long comment in android_sdk.py (E501).
Add a third Distributing tab covering styled DMGs (background image, app-left/Applications-right layout) via dmgbuild: verified settings file matched to flet's build output, the >=1.6.7 version floor (older versions produce blank backgrounds on macOS 26.2+, dmgbuild#273), the @2x Retina background convention, and the same sign/notarize/staple closing sequence as the plain-DMG tab. dmgbuild writes the Finder layout without a GUI session, so the recipe works identically in CI.
…nsion - Credentials split into two sequential subsections (Creating a credential / Providing it to Flet); note that only the keychain profile takes both credential kinds. - CI section now walks the one-time setup: .p12 export from Keychain Access, why the .p12 is base64-encoded while the PEM .p8 goes in as-is, and gh secret set commands for all six secrets. Workflow writes the API key through $APPLE_API_KEY into runner.temp (printf, no path duplication, out of artifact-upload reach) with a note on why the key must be a file at all. - Distributing: DMG (plain or dmgbuild custom look, sharing the sign/notarize/staple steps) vs zip as tabs, each step explained; zip's can't-be-stapled asymmetry called out. - Troubleshooting: two new rows (codesign hang on keychain prompt, missing Apple intermediate CAs) and a Mac App Store table (ITMS-90889, 91109 quarantine, App Sandbox file access, TestFlight app relocation) — all hit during real-world verification.
Bring the Symptom | Cause-and-fix table format from the macOS page to the other platform pages. Rows are symptom indexes: where a page already documents a failure in depth (android's Extract packages, linux's Wayland section), the row links there instead of duplicating it. - windows: tabulate the Developer Mode item; add the missing C++-workload toolchain error. - linux: linker error (lld), missing -dev packages, libmpv/GStreamer as *runtime* dependencies on users' machines, Wayland positioning. - ios (had none): codesign hang on hidden keychain prompt, no valid signing certificates, profiles disappearing while Xcode runs. - android: sitepackages.zip crash -> Extract packages, keytool not on PATH, manifest merger provider clash. Also link the macOS CI section to the complete build workflow in the CI/CD guide — its snippet shows only the signing-specific steps.
Use the expandable # (N)! annotations (remark-code-annotations, as on the CI/CD guide) where explanations belong to specific lines: - CI workflow: import-codesign-certs (with docs link), APPLE_API_KEY path/runner.temp rationale, optional identity secret (auto-discovery), printf materialization — absorbing the two after-block explainer paragraphs and the tip admonition. - gh secret set: base64-for-binary .p12 vs as-is PEM .p8, interactive prompt note, which values come with the API key. - DMG sign/notarize/staple: the three per-command bullets. Left plain: altool (backslash-continued lines can't carry trailing markers), dmg_settings.py (copied to disk, comments should survive), and short config examples.
The flet pack command is supported (the planned deprecation was reversed) but its only doc was the archived PyInstaller page, hidden from the sidebar and opening with a danger banner steering readers to flet build. Replace it with a live page under Publishing (sidebar: 'Using PyInstaller', below Web): - Neutral 'How it relates to flet build' comparison table with when-to-pick-which guidance instead of the danger banner. - Content refreshed against today's pack.py: platform-native icon formats (.ico/.icns/.png) replace the old PNG+Pillow advice, the Windows-only ';' add-data separator is gone, and --onedir, --yes, --company-name, --debug-console, --uac-admin, --codesign-identity and --pyinstaller-build-args are covered. Documents that icon and metadata are patched into the embedded Flet viewer too, and the build/dist deletion prompts. - AppVeyor CI section (stale claims, PAT-in-yaml flow, missing images) replaced by a GitHub Actions three-OS matrix. - Troubleshooting table (hidden imports, --debug-console, macOS Gatekeeper) in the same format as the platform pages. - Every option mention links to its anchor in the flet pack CLI reference, per the house style. - Alternative-note admonitions on the macOS/Windows/Linux pages and a pointer in the publish index; archived page removed (nothing linked to it).
# Conflicts: # CHANGELOG.md
flet build): macOS code signing and notarizationflet build): macOS code signing, notarization and AppStore support
| for key, value in (self.get_pyproject("tool.flet.macos.signing") or {}).items(): | ||
| if isinstance(value, dict) and key not in self.MACOS_DISTRIBUTIONS: |
| if distribution == "app-store": | ||
| resolve_identity(identity, types=APP_STORE_CERTIFICATE_TYPES) | ||
| resolve_identity( | ||
| self.macos_signing_setting( | ||
| cli_value=self.options.macos_installer_identity, | ||
| distribution=distribution, | ||
| key="installer_identity", | ||
| env_var="FLET_MACOS_INSTALLER_IDENTITY", | ||
| ), | ||
| policy="basic", | ||
| types=INSTALLER_CERTIFICATE_TYPES, | ||
| ) | ||
| elif distribution == "developer-id": | ||
| resolve_identity(identity, types=DEVELOPER_ID_CERTIFICATE_TYPES) | ||
| self._macos_notary_credentials() | ||
| else: | ||
| resolve_identity(identity) |
| # Quarantine and Finder-info extended attributes make codesign fail with | ||
| # "resource fork, Finder information, or similar detritus not allowed", | ||
| # and App Store Connect rejects quarantined files outright. | ||
| _run(["xattr", "-cr", str(app_path)]) |
[skip ci]
flet build macoscan now produce a Developer ID-signed, notarized, and stapled app for direct distribution — and a Mac App Store / TestFlight-ready.pkg— selected by a single distribution lane switch. Previously the output was ad-hoc signed with no hardened runtime, which macOS 15+ effectively blocks for downloaded apps, and native.sofiles from dependencies triggered "library load disallowed by system policy". Signing is a no-op unless configured, so existing builds are unaffected.Closes #2347
Closes #4543
Usage
The minimal form — the signing identity is auto-discovered from the keychain:
One
pyproject.tomlcan hold both lanes; lane-specific settings live in per-lane subtables and don't collide:Every setting resolves CLI > per-lane subtable > flat pyproject key > env var (
FLET_MACOS_*,APPLE_API_*for notary credentials in CI).What it does
New
flet_cli/utils/macos_sign.py, orchestrated from the build command:--macos-distribution {none,developer-id,app-store}replaces per-feature toggles, so conflicting configurations are inexpressible. Identity resolution is scoped to the certificate type each lane requires, and auto-discovers the certificate when the keychain holds exactly one of that type.developer-id: every bundled Mach-O (found by magic bytes, not extension) is signed inside-out with the hardened runtime and secure timestamp; entitlements applied to the app and helper executables; verified withcodesign --verify --deep --strictplus a per-binary coverage assertion; thennotarytool submit --wait(keychain profile or ASC API key), Apple's per-file log on rejection, staple + validate.app-store: signs with Apple Distribution — sandboxed, no hardened runtime, store-mandatedapplication-identifier/team-identifierentitlements, helpers get the sandboxinheritpair, allcom.apple.security.cs.*exceptions stripped — embeds the provisioning profile (scrubbed of the quarantine xattr that ASC processing rejects as error 91109;--validate-appmisses it), and packages an installer-signed.pkgverified withpkgutil.LSApplicationCategoryType— all fail in seconds with the exact setting to fix.Breaking change
iOS per-method signing settings in
[tool.flet.ios.export_methods.<method>]now override the flat[tool.flet.ios]keys instead of losing to them (previously the subtables were dead config whenever a flat key existed). Same rule as the new macOS per-lane subtables. See the migration guide.Also in this PR
com.apple.security.cs.allow-unsigned-executable-memory— required for ctypes/cffi callbacks on x86_64 under the hardened runtime (overridable).<true />(with a space), accepted by Xcode/plutil but rejected by codesign's AMFI parser; both templates fixed and the signer normalizes entitlements throughplistlib.FLET_WEB_RENDERER/FLET_WEB_ROUTE_URL_STRATEGY/FLET_WEB_NO_CDNfallbacks now reachable inflet buildandflet publish; case normalized on the resolved value.flet pack) guide revived as Using PyInstaller; new env vars in the reference page.Testing
spctlreports "Notarized Developer ID"; a quarantined download of the app launches cleanly and imports pydantic/numpy/cryptography/pillow/opencv under the hardened runtime — the macOS app throws "library load disallowed by system policy" error while loading 3rd-party Python.somodules #4543 scenario, dead..pkgfrom a plainflet build macospassedaltool --validate-appand--upload-package, survived ASC processing, and the TestFlight-installed app runs all native imports under the App Sandbox.