Nour 93cba59cee
flutter_tools: Auto-generate ExportOptions.plist for manual iOS code signing (#177888)
# 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

- [x] I have signed the CLA
- [x] I read the Contributor Guide and Tree Hygiene docs
- [x] I linked the issue this fixes (#177853) and explained the failure
scenario
- [x] I added documentation and trace logs so developers understand what
changed
- [x] I added comprehensive unit tests for the new behavior
- [x] All existing and new tests pass locally (`flutter test
packages/flutter_tools`)
- [x] Code passes linting (`dart analyze`)

## Safety Notes

- [x] This code only runs for manual code signing in `flutter build ipa`
for Release/Profile builds
- [x] Debug builds and automatic signing behavior are completely
unchanged
- [x] If no provisioning profile UUID is found, we silently fall back to
the old exportOptions.plist logic instead of failing
- [x] Added trace-level logging explaining success/fallback scenarios
for debugging
- [x] No breaking changes - existing workarounds continue to work
- [x] 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
- #106612 - Support `flutter build ipa` with manual signing and
provisioning profiles
- #113977 - Flutter build IPA with --export-options-plist not working

---------

Co-authored-by: Elijah Okoroh <okorohelijah@google.com>
2026-01-08 12:09:39 +00:00
..