Skip to content

flutter_tools: Auto-generate ExportOptions.plist for manual iOS code signing#177888

Merged
auto-submit[bot] merged 16 commits intoflutter:masterfrom
MohammedTarigg:improve-ios-export-options-manual-signing
Jan 8, 2026
Merged

flutter_tools: Auto-generate ExportOptions.plist for manual iOS code signing#177888
auto-submit[bot] merged 16 commits intoflutter:masterfrom
MohammedTarigg:improve-ios-export-options-manual-signing

Conversation

@MohammedTarigg
Copy link
Contributor

flutter_tools: Auto-generate ExportOptions.plist for manual iOS code signing

Summary

Enhance flutter build ipa to automatically generate a complete ExportOptions.plist for manual code signing configurations, eliminating the need for users to create and maintain one manually.

Fixes: #177853

Problem

When using manual code signing (CODE_SIGN_STYLE=Manual) with flutter build ipa for Release/Profile builds, the archive succeeds but the export step fails with:

error: exportArchive: "Runner.app" requires a provisioning profile with the Push Notifications and Sign in with Apple features.

Root cause: Flutter generated an incomplete ExportOptions.plist that only included method and uploadBitcode. When using manual signing, xcodebuild -exportArchive requires:

  • teamID (from project's DEVELOPMENT_TEAM)
  • signingStyle=manual
  • provisioningProfiles (mapping bundle ID to provisioning profile UUID)

Without these, Xcode cannot resolve the provisioning profile for export, even if the profile is correctly configured and contains the required entitlements.

Solution

Enhanced _createExportPlist() in BuildIOSArchiveCommand to automatically generate a complete ExportOptions.plist when:

  1. CODE_SIGN_STYLE=Manual is detected for the main app target
  2. Build mode is Release or Profile (production builds only)
  3. A valid provisioning profile can be located and parsed

The generated plist includes:

  • method (app-store, ad-hoc, enterprise, etc. - respects user's --export-method flag)
  • teamID (from DEVELOPMENT_TEAM build setting)
  • signingStyle=manual
  • provisioningProfiles mapping main app bundle ID to provisioning profile UUID

Fallback behavior:

  • If profile lookup fails: silently fall back to simple plist (no regression)
  • For Automatic signing: unchanged behavior
  • For Debug builds: unchanged behavior (not App Store export)
  • For multi-target apps (extensions): falls back to simple plist today (see TODO below)

Changes

Core Implementation

  • Added ProfileData class to encapsulate provisioning profile info (UUID and name)
  • Refactored profile handling into reusable static methods for testability
  • Enhanced _createExportPlist() with manual signing detection and profile lookup
  • Added _findProvisioningProfileUuid() to locate provisioning profiles by specifier
  • Added _parseProvisioningProfileInfo() to decode provisioning profile data once
  • Added comprehensive trace logging for debugging

Testing

  • Created build_ipa_export_plist_test.dart with 7 unit tests covering:
    • Manual signing with profile found → enhanced plist generated ✓
    • Automatic signing → simple plist (unchanged behavior) ✓
    • Debug builds → simple plist (unchanged behavior) ✓
    • Different export methods → respected in generated plist ✓
    • Profile lookup failures → fallback to simple plist ✓
    • Null codeSignStyle → handled gracefully ✓
  • All existing flutter_tools tests continue to pass

Documentation

Added extensive inline comments explaining:

  • Enhancement conditions and fallback behavior
  • Provisioning profile search strategy and error handling
  • Performance and security considerations
  • Future enhancements (multi-target support)

Safety & Scope

What This Fixes

  • ✅ Manual signing export failures for apps with Push Notifications / Sign in with Apple
  • ✅ Auto-generates correct plist for any entitlements covered by provisioning profile
  • ✅ Unblocks users from manual --export-options-plist workaround
  • ✅ Improves iOS build UX for enterprise teams doing manual signing

What This Does NOT Change

  • ✅ Automatic signing behavior (unchanged)
  • ✅ Debug builds (unchanged)
  • ✅ CLI interface (no new flags)
  • ✅ Ad-hoc / enterprise distributions (unchanged if not using manual signing)

Known Limitations (Future Enhancements)

  • Currently only supports single-target apps (main app bundle ID only)
  • TODO: Multi-target apps with extensions (notification service, widgets, watch, etc.) - each extension target bundle ID may need separate entry in provisioningProfiles dict
  • TODO: Support for multiple provisioning profiles if app uses multiple signing identities

Pre-merge Checklist

Safety Notes

  • This code only runs for manual code signing in flutter build ipa for Release/Profile builds
  • Debug builds and automatic signing behavior are completely unchanged
  • If no provisioning profile UUID is found, we silently fall back to the old exportOptions.plist logic instead of failing
  • Added trace-level logging explaining success/fallback scenarios for debugging
  • No breaking changes - existing workarounds continue to work
  • Minimal blast radius - only affects manual signing export path

Verification

Users can verify the fix by:

  1. Creating an iOS app with CODE_SIGN_STYLE=Manual, DEVELOPMENT_TEAM set, and Push Notifications entitlements
  2. Running flutter build ipa --release --verbose
  3. Confirming the trace log shows "Generated ExportOptions.plist with teamID, signingStyle=manual, and provisioningProfiles"
  4. Verifying the IPA is successfully created without needing --export-options-plist

Related Issues

@MohammedTarigg MohammedTarigg requested a review from a team as a code owner November 1, 2025 20:40
@github-actions github-actions bot added tool Affects the "flutter" command-line tool. See also t: labels. team-ios Owned by iOS platform team labels Nov 1, 2025
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request enhances flutter build ipa to automatically generate a complete ExportOptions.plist for manual code signing configurations. This is a significant improvement for developers using manual signing, especially for apps with entitlements like Push Notifications. The changes include adding logic to detect manual signing, find the correct provisioning profile, and generate an enhanced plist with the necessary signing information. The implementation includes robust fallback mechanisms and extensive logging. New unit tests have been added to cover the new logic, and existing tests are updated. The code is well-documented and follows good practices for testability and maintainability. I have a couple of minor suggestions for improving code style and readability.

MohammedTarigg added a commit to MohammedTarigg/flutter that referenced this pull request Nov 1, 2025
…est practices

- Simplify FakeXcodeProjectInterpreter.getBuildSettings by defining defaults
  first, then spreading overrides (eliminates redundant ?? operators)
- Replace listSync() with await for (using list()) in _findProvisioningProfileUuid
  for better asynchronous code style and non-blocking file system operations

Addresses code review feedback from PR flutter#177888
MohammedTarigg added a commit to MohammedTarigg/flutter that referenced this pull request Nov 1, 2025
…iene

Add comments directly above skip parameters explaining why integration
tests are skipped. This satisfies Flutter's tree hygiene requirement
that all skipped tests must have justification comments.

Fixes CI failure in PR flutter#177888
MohammedTarigg added a commit to MohammedTarigg/flutter that referenced this pull request Nov 2, 2025
…est practices

- Simplify FakeXcodeProjectInterpreter.getBuildSettings by defining defaults
  first, then spreading overrides (eliminates redundant ?? operators)
- Replace listSync() with await for (using list()) in _findProvisioningProfileUuid
  for better asynchronous code style and non-blocking file system operations

Addresses code review feedback from PR flutter#177888
MohammedTarigg added a commit to MohammedTarigg/flutter that referenced this pull request Nov 2, 2025
…iene

Add comments directly above skip parameters explaining why integration
tests are skipped. This satisfies Flutter's tree hygiene requirement
that all skipped tests must have justification comments.

Fixes CI failure in PR flutter#177888
@MohammedTarigg MohammedTarigg force-pushed the improve-ios-export-options-manual-signing branch from 8d1771c to 959303b Compare November 2, 2025 10:26
MohammedTarigg added a commit to MohammedTarigg/flutter that referenced this pull request Nov 2, 2025
…est practices

- Simplify FakeXcodeProjectInterpreter.getBuildSettings by defining defaults
  first, then spreading overrides (eliminates redundant ?? operators)
- Replace listSync() with await for (using list()) in _findProvisioningProfileUuid
  for better asynchronous code style and non-blocking file system operations

Addresses code review feedback from PR flutter#177888
MohammedTarigg added a commit to MohammedTarigg/flutter that referenced this pull request Nov 2, 2025
…iene

Add comments directly above skip parameters explaining why integration
tests are skipped. This satisfies Flutter's tree hygiene requirement
that all skipped tests must have justification comments.

Fixes CI failure in PR flutter#177888
@MohammedTarigg MohammedTarigg force-pushed the improve-ios-export-options-manual-signing branch from cfd6ca6 to 0ac20f1 Compare November 2, 2025 15:52
MohammedTarigg added a commit to MohammedTarigg/flutter that referenced this pull request Nov 3, 2025
…est practices

- Simplify FakeXcodeProjectInterpreter.getBuildSettings by defining defaults
  first, then spreading overrides (eliminates redundant ?? operators)
- Replace listSync() with await for (using list()) in _findProvisioningProfileUuid
  for better asynchronous code style and non-blocking file system operations

Addresses code review feedback from PR flutter#177888
MohammedTarigg added a commit to MohammedTarigg/flutter that referenced this pull request Nov 3, 2025
…iene

Add comments directly above skip parameters explaining why integration
tests are skipped. This satisfies Flutter's tree hygiene requirement
that all skipped tests must have justification comments.

Fixes CI failure in PR flutter#177888
@MohammedTarigg MohammedTarigg force-pushed the improve-ios-export-options-manual-signing branch from f9b8aa3 to a10a5f0 Compare November 3, 2025 06:27
@MohammedTarigg MohammedTarigg force-pushed the improve-ios-export-options-manual-signing branch from 028bcb9 to b3a900b Compare November 8, 2025 10:18
MohammedTarigg added a commit to MohammedTarigg/flutter that referenced this pull request Nov 8, 2025
…est practices

- Simplify FakeXcodeProjectInterpreter.getBuildSettings by defining defaults
  first, then spreading overrides (eliminates redundant ?? operators)
- Replace listSync() with await for (using list()) in _findProvisioningProfileUuid
  for better asynchronous code style and non-blocking file system operations

Addresses code review feedback from PR flutter#177888
@MohammedTarigg MohammedTarigg force-pushed the improve-ios-export-options-manual-signing branch from b3a900b to 7c71a1f Compare November 8, 2025 10:23
MohammedTarigg added a commit to MohammedTarigg/flutter that referenced this pull request Nov 8, 2025
…iene

Add comments directly above skip parameters explaining why integration
tests are skipped. This satisfies Flutter's tree hygiene requirement
that all skipped tests must have justification comments.

Fixes CI failure in PR flutter#177888
Copy link
Contributor

@vashworth vashworth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the contribution! Overall the concept sounds good to me. I left some comments on tweaking the implementation to make it more testable.

@MohammedTarigg MohammedTarigg force-pushed the improve-ios-export-options-manual-signing branch from a9e070d to 7c71a1f Compare November 14, 2025 10:31
@fluttergithubbot
Copy link
Contributor

An existing Git SHA, 7c71a1f7e35b45bfb7f476276f41ae920b91b09e, was detected, and no actions were taken.

To re-trigger presubmits after closing or re-opeing a PR, or pushing a HEAD commit (i.e. with --force) that already was pushed before, push a blank commit (git commit --allow-empty -m "Trigger Build") or rebase to continue.

MohammedTarigg added a commit to MohammedTarigg/flutter that referenced this pull request Nov 14, 2025
- Remove design decision comment from BuildIOSArchiveCommand class doc
- Remove empty integration test placeholders (move rationale to PR description)
- Remove redundant buildInfo declaration (line 575)
- Make createExportPlist public with buildSettings parameter
- Fetch buildSettings at call site and pass to public method
- Remove test-only createExportPlistForTesting method
- Remove unnecessary private wrapper methods
- Update tests to use public createExportPlist method via command instance
- Update all tests to pass buildSettings instead of individual parameters

Follows Flutter's style guide: test APIs belong in test frameworks, not in the codebase.
MohammedTarigg added a commit to MohammedTarigg/flutter that referenced this pull request Nov 14, 2025
- Remove design decision comment from BuildIOSArchiveCommand class doc
- Remove empty integration test placeholders (move rationale to PR description)
- Remove redundant buildInfo declaration (line 575)
- Make createExportPlist public with buildSettings parameter
- Fetch buildSettings at call site and pass to public method
- Remove test-only createExportPlistForTesting method
- Remove unnecessary private wrapper methods
- Update tests to use public createExportPlist method via command instance
- Update all tests to pass buildSettings instead of individual parameters

Follows Flutter's style guide: test APIs belong in test frameworks, not in the codebase.
@MohammedTarigg MohammedTarigg force-pushed the improve-ios-export-options-manual-signing branch from 4070b45 to afa4f9a Compare November 14, 2025 10:36
MohammedTarigg added a commit to MohammedTarigg/flutter that referenced this pull request Nov 14, 2025
- Remove design decision comment from BuildIOSArchiveCommand class doc
- Remove empty integration test placeholders (move rationale to PR description)
- Remove redundant buildInfo declaration (line 575)
- Make createExportPlist public with buildSettings parameter
- Fetch buildSettings at call site and pass to public method
- Remove test-only createExportPlistForTesting method
- Remove unnecessary private wrapper methods
- Update tests to use public createExportPlist method via command instance
- Update all tests to pass buildSettings instead of individual parameters

Follows Flutter's style guide: test APIs belong in test frameworks, not in the codebase.
…iveCommand

- Introduced ProfileData class to encapsulate provisioning profile UUID and name.
- Updated _findProvisioningProfileUuid to utilize ProfileData for improved clarity and efficiency.
- Enhanced _parseProvisioningProfileInfo to extract both UUID and name in a single operation.
- Added trace logging for better debugging when provisioning profiles cannot be found.
- Removed unused ProcessManager and PlistParser parameters from method signatures in BuildIOSArchiveCommand.

This refactor streamlines provisioning profile management and improves logging for manual signing scenarios.
…visioning profile handling

- Added detailed documentation for the ProfileData class and its usage in generating ExportOptions.plist.
- Implemented logic to auto-generate an enhanced ExportOptions.plist for manual signing scenarios, ensuring compatibility with Xcode's requirements.
- Improved logging for provisioning profile lookups, providing clearer trace messages for debugging.
- Expanded unit tests to cover various manual signing scenarios, including fallback behaviors and different build modes.
…est practices

- Simplify FakeXcodeProjectInterpreter.getBuildSettings by defining defaults
  first, then spreading overrides (eliminates redundant ?? operators)
- Replace listSync() with await for (using list()) in _findProvisioningProfileUuid
  for better asynchronous code style and non-blocking file system operations

Addresses code review feedback from PR flutter#177888
…iene

Add comments directly above skip parameters explaining why integration
tests are skipped. This satisfies Flutter's tree hygiene requirement
that all skipped tests must have justification comments.

Fixes CI failure in PR flutter#177888
- Made createExportPlist public with buildSettings parameter
- Removed test-only createExportPlistForTesting method
- Eliminated design documentation from class docs
- Cleaned up empty test placeholders
- Fixed logging to use logger instead of globals.logger
- Extracted provisioning profile directory lookup into reusable method

Tests now call the public method directly with mock dependencies.
Removed static _createSimpleExportPlistStatic and _createManualSigningExportPlistStatic methods and converted them to instance methods _createSimpleExportPlist and _createManualSigningExportPlist. This follows Flutter's testing best practices by avoiding test-only static methods.
Addresses code review feedback on PR flutter#177888 by extracting shared
provisioning profile logic into reusable components.

Changes:
- Made ProvisioningProfile class public and added uuid field
- Made parseProvisioningProfile() method public in XcodeCodeSigningSettings
- Extracted getProvisioningProfileDirectory() as a public function
- Updated build_ios.dart to use shared code from code_signing.dart
- Removed ProfileData class and duplicate parsing methods

This eliminates approximately 73 lines of duplicate code and establishes
a single source of truth for provisioning profile handling across
flutter_tools.
…sues

- Refactor XcodeCodeSigningSettings instantiation to runCommand and pass it to createExportPlist for dependency injection.
- Remove core_devices_test.dart import in build_ipa_export_plist_test.dart to fix analyzer failure.
- Implement local FakeXcodeCodeSigningSettings, FakeBuildableIOSApp, and FakeIosProject in build_ipa_export_plist_test.dart using noSuchMethod for robust, self-contained testing.
- Remove stale TODO comments in build_ipa_test.dart and build_ipa_export_plist_test.dart.
The test for generating enhanced ExportOptions.plist for manual signing
was failing because _findProvisioningProfileUuid used globals.fs and
globals.fsUtils instead of the injected FileSystem, making it impossible
to test with MemoryFileSystem.

Changes:
- Add optional fileSystemUtils parameter to createExportPlist
- Add required fileSystem and fileSystemUtils parameters to
  _findProvisioningProfileUuid
- Update test to create provisioning profiles directory structure in
  MemoryFileSystem and pass FakeFileSystemUtils with home directory path
- Add FakeFileSystemUtils class to test file

The changes are backwards compatible - existing code that doesn't pass
fileSystemUtils will continue to use globals.fsUtils as the default.
@okorohelijah
Copy link
Contributor

@okorohelijah review

Copy link
Contributor

@okorohelijah okorohelijah left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@auto-submit
Copy link
Contributor

auto-submit bot commented Jan 7, 2026

autosubmit label was removed for flutter/flutter/177888, because The base commit of the PR is older than 7 days and can not be merged. Please merge the latest changes from the main into this branch and resubmit the PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

platform-ios iOS applications specifically team-ios Owned by iOS platform team tool Affects the "flutter" command-line tool. See also t: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants