fix(verify): resolve iOS bundle id per target, not first pbxproj match - #4
Merged
Merged
Conversation
IosParser resolved $(PRODUCT_BUNDLE_IDENTIFIER) with a file-global heuristic
(drop values containing "Test", take the first remaining match), which is blind
to which target a value belongs to. In a multi-target project (app +
notification / widget extensions + tests) an extension's bundle id contains no
"Test", so the app's $(PRODUCT_BUNDLE_IDENTIFIER) could resolve to the
extension's depending on pbxproj ordering -- the false positive behind the
"no local target matches ULink bundle ID" failures, and a wrong bundle id for
the Flutter path (which does not use target discovery at all).
Resolution is now scoped to the target that owns the Info.plist being parsed:
1. the build configuration whose INFOPLIST_FILE points at that plist,
2. the application target (com.apple.product-type.application),
3. the legacy first-non-test heuristic as a last resort (unchanged behaviour
for single-target projects).
Adds a small brace-balanced pbxproj micro-parser and multi-target tests,
including one where the extension config precedes the app config.
This was referenced Sep 6, 2026
The tier-2 fallback (_bundleIdForApplicationTarget, used when no INFOPLIST_FILE points at the plist) matched the substring 'com.apple.product-type.application', which also matches '...application.watchapp2' and '...application.on-demand-install-capable' (App Clip). In an app+watch or app+clip project with no INFOPLIST_FILE and the watch/clip target listed first, PRODUCT_BUNDLE_IDENTIFIER resolved to the wrong target - the same false positive this fix set out to eliminate. Match the exact double-quoted product type instead; Xcode always writes it quoted, so the trailing quote excludes the watch/clip subtypes. Adds a multi-application-target regression test.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
IosParser._extractVariableFromPbxprojresolved$(PRODUCT_BUNDLE_IDENTIFIER)with a file-global heuristic: match everyPRODUCT_BUNDLE_IDENTIFIER = …;in the pbxproj, drop values containingTest, return the first remaining in file order. That is blind to which target a value belongs to.In a multi-target project (app + notification / widget extensions + tests), an extension's id — e.g.
dev.rart.abrezo.app.NotificationServiceExtension— contains noTest, so whichever build-config block Xcode wrote first wins. The app's$(PRODUCT_BUNDLE_IDENTIFIER)could resolve to an extension's id. This is the false positive behind the "no local target matches ULink bundle ID" hard failure (verify_command.dart), and — because the Flutter path parsesRunner/Info.plistdirectly and does not use target discovery — a silently wrong bundle id for every Flutter app with extensions.Runner/Info.plistuses$(PRODUCT_BUNDLE_IDENTIFIER)by default, so this path runs for essentially every Flutter/iOS project.Fix
Resolution is scoped to the target that owns the Info.plist being parsed:
INFOPLIST_FILEpoints at this exact plist. The app target's configs carryINFOPLIST_FILE = Runner/Info.plist; extensions/tests use their own path orGENERATE_INFOPLIST_FILE, so this disambiguates cleanly.PBXNativeTarget(com.apple.product-type.application) →XCConfigurationList→XCBuildConfigurationwhen noINFOPLIST_FILEmatches.Implemented as a small brace-balanced pbxproj micro-parser (private statics in
ios_parser.dart, no new deps); every strategy is defensive and falls through to the next on any parse miss. Consumers (FlutterParser,ProjectDetector.discoverTargetByBundleId) callparseInfoPlistand improve transparently — no consumer changes.Tests
parseInfoPlist(Runner/Info.plist)resolves the app id.parseInfoPlist(NotificationService/Info.plist)resolves the extension id.INFOPLIST_FILElines resolves the app id via the productType walk.dart analyze lib testclean.dart test— 295 pass.Context
Follow-up to #3 (verify disclosure + per-platform scheme reporting). Independent branch off
main; no overlap with #3.