-
-
Notifications
You must be signed in to change notification settings - Fork 353
Removing a target referenced by PBXFileSystemSynchronizedBuildFileExceptionSet crashes serialization #1093
Description
Summary
Calling removeFromProject() on a PBXNativeTarget does not cascade to PBXFileSystemSynchronizedBuildFileExceptionSet objects that reference that target. The orphaned exception sets retain a nil target reference, which crashes during serialization when generating the plist annotation (target.name on nil).
Minimal reproduction
Setup (Xcode 16+):
- Create a project with two app targets: AppA and AppB
- Create a folder
SharedSrc/containingShared.swiftandInfo.plist - In Xcode, drag
SharedSrc/into the project as a synchronized folder reference, with both targets checked - Select
Info.plistin the navigator, open the File Inspector, and uncheck AppB from Target Membership
This creates a PBXFileSystemSynchronizedBuildFileExceptionSet with target = AppB inside the SharedSrc synchronized root group — meaning "exclude Info.plist from AppB's build."
Reproduction (Ruby xcodeproj gem v1.27.0):
require "xcodeproj"
proj = Xcodeproj::Project.open("Repro.xcodeproj")
# Removing AppA works fine (not referenced by any exception set)
proj.targets.find { |t| t.name == "AppA" }.remove_from_project
proj.save # => OK
# Removing AppB crashes (referenced by the exception set)
proj.targets.find { |t| t.name == "AppB" }.remove_from_project
proj.save # => CRASHCrash:
PBXFileSystemSynchronizedBuildFileExceptionSet#display_name: undefined method 'name' for nil (NoMethodError)
"Exceptions for \"#{GroupableHelper.parent(self).display_name}\" in \"#{target.name}\" target"
What happens
removeFromProject()on the target removes thePBXNativeTargetobject, its build configuration list, and its build phases- It does not remove
PBXFileSystemSynchronizedBuildFileExceptionSetobjects whosetargetproperty pointed to the removed target - On save, the serializer calls
display_nameon each exception set, which accessestarget.name— now nil — and crashes
What should happen
removeFromProject() on a PBXNativeTarget should also remove any PBXFileSystemSynchronizedBuildFileExceptionSet whose target references the removed target. Xcode's UI does this cleanup when deleting a target — the programmatic API should match.
Additional incomplete cascading
Beyond exception sets, removeFromProject() also leaves behind:
PBXFileReferencefor the target's product (.app,.xctest)- The product reference in the Products group
PBXGroupentries for the target's source directory
These don't crash serialization but leave orphaned entries in the project file.
Workaround
Clean up orphaned exception sets manually before saving:
proj.objects.select { |o|
o.isa == "PBXFileSystemSynchronizedBuildFileExceptionSet" &&
o.respond_to?(:target) && o.target.nil?
}.each { |o| o.remove_from_project }Environment
- Ruby xcodeproj gem 1.27.0
- Project created with Xcode 26 beta (uses synchronized folders /
PBXFileSystemSynchronizedRootGroup) - Also reproduced via xcodeproj-mcp-server v1.5.0, which uses the Swift tuist/XcodeProj library — same crash (exit code 133 / SIGTRAP, the Swift equivalent of nil access)