Skip to content

Commit

Permalink
[PBXFileReference] Removes all items related to an external project.
Browse files Browse the repository at this point in the history
Test case thanks to @jpsim.
  • Loading branch information
alloy committed Jul 21, 2014
1 parent e5a69ed commit 9ce8ac2
Show file tree
Hide file tree
Showing 9 changed files with 198 additions and 12 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@

## master

###### Enhancements

* [PBXFileReference] If a file reference represents an external Xcode project
and is removed from the project then all items related to the external
project will also be removed.
[JP Simard](https://github.com/jpsim)
[Eloy Durán](https://github.com/alloy)
[Xcodeproj#158](https://github.com/CocoaPods/Xcodeproj/issues/158)
[Xcodeproj#161](https://github.com/CocoaPods/Xcodeproj/pull/161)

###### Bug fixes

* [Workspace] Fixed adding a project to a workspace.
Expand Down
2 changes: 1 addition & 1 deletion lib/xcodeproj/project.rb
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@ def new(klass)
# @note Implementation detail: as objects usually are created serially
# this method creates a batch of UUID and stores the not colliding
# ones, so the search for collisions with known UUIDS (a
# performance bottleneck) is performed is performed less often.
# performance bottleneck) is performed less often.
#
# @return [String] A UUID unique to the project.
#
Expand Down
4 changes: 2 additions & 2 deletions lib/xcodeproj/project/object.rb
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,8 @@ def add_referrer(referrer)
end

# Informs the object that another object stopped referencing it. If the
# object has no other references it is removed form project UUIDs hash
# because it is unreachable.
# object has no other references it is removed from the project UUIDs
# hash because it is unreachable.
#
# @return [void]
#
Expand Down
1 change: 1 addition & 0 deletions lib/xcodeproj/project/object/container_item_proxy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class PBXContainerItemProxy < AbstractObject
# If this assumption is incorrect, there could be loss of
# information opening and saving an existing project.
#
# TODO this is the external reference that 'contains' other proxy items
attribute :container_portal, String

# @return [String] the type of the proxy.
Expand Down
86 changes: 86 additions & 0 deletions lib/xcodeproj/project/object/file_reference.rb
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,8 @@ def set_explicit_file_type(type = nil)
end
end

#---------------------------------------------------------------------#

# Checks whether the reference is a proxy.
#
# @return [Bool] always false for this ISA.
Expand All @@ -233,6 +235,90 @@ def proxy?
false
end

# If this file reference represents an external Xcode project reference
# then this will return metadata about it which includes the reference
# to the 'Products' group that's created in this project (the project
# that includes the external project).
#
# @return [ObjectDictionary, nil] The external project metadata for
# this file reference or `nil` if it's not an external project.
#
def project_reference_metadata
project.root_object.project_references.find do |project_reference|
project_reference['ProjectRef'] == self
end
end

# If this file reference represents an external Xcode project reference
# then this will return the objects that are 'containers' for items
# contained in the external Xcode project.
#
# @return [Array<PBXContainerItemProxy>] The containers for items in
# the external Xcode project.
#
def proxy_containers
project.objects.select do |object|
object.isa == 'PBXContainerItemProxy' &&
object.container_portal == self.uuid
end
end

# If this file reference represents an external Xcode project reference
# then this will return proxies for file references contained in the
# external Xcode project.
#
# @return [Array<PBXReferenceProxy>] The file reference proxies for
# items located in the external Xcode project.
#
def file_reference_proxies
containers = proxy_containers
if containers.empty?
[]
else
project.objects.select do |object|
object.isa == 'PBXReferenceProxy' &&
containers.include?(object.remote_ref)
end
end
end

# If this file reference represents an external Xcode project reference
# then this will return dependencies on targets contained in the
# external Xcode project.
#
# @return [Array<PBXTargetDependency>] The dependencies on targets
# located in the external Xcode project.
#
def target_dependency_proxies
containers = proxy_containers
if containers.empty?
[]
else
project.objects.select do |object|
object.isa == 'PBXTargetDependency' &&
containers.include?(object.target_proxy)
end
end
end

# In addition to removing the file reference, this will also remove any
# items related to this reference in case it represents an external
# Xcode project.
#
# @see AbstractObject#remove_from_project
#
# @return [void]
#
def remove_from_project
if project_reference = project_reference_metadata
file_reference_proxies.each(&:remove_from_project)
target_dependency_proxies.each(&:remove_from_project)
project_reference['ProductGroup'].remove_from_project
project.root_object.project_references.delete(project_reference)
end
super
end

#---------------------------------------------------------------------#

end
Expand Down
8 changes: 4 additions & 4 deletions lib/xcodeproj/project/object_attributes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ class AbstractObjectAttribute
# the class that owns the attribute.
#
def initialize(type, name, owner)
@type = type
@name = name
@owner = owner
@type = type
@name = name
@owner = owner
end

# @return[String] The name of the attribute in camel case.
Expand All @@ -65,7 +65,7 @@ def plist_name
end
end

# @return [Hash] a shared store which cahces the plist name of the
# @return [Hash] a shared store which caches the plist name of the
# attributes.
#
def self.plist_name_store
Expand Down
2 changes: 1 addition & 1 deletion lib/xcodeproj/project/object_dictionary.rb
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def delete(key)
#
def to_hash
result = {}
each { |key, obj| result[key] = obj.uuid }
each { |key, obj| result[key] = obj.uuid if obj }

This comment has been minimized.

Copy link
@alloy

alloy Jul 22, 2014

Author Member

Hmm, actually I now wonder if this should not be removed again.

/cc @irrationalfab

This comment has been minimized.

Copy link
@fabiopelosin

fabiopelosin Jul 22, 2014

Member

This should be removed in my opinion because it hides bugs.

This comment has been minimized.

Copy link
@fabiopelosin

fabiopelosin Jul 22, 2014

Member

I forgot to check that it was removed in your patch.

This comment has been minimized.

Copy link
@alloy

alloy Jul 22, 2014

Author Member

Yeah agreed, it should be removed. Will do that now.

This comment has been minimized.

Copy link
@jpsim

jpsim Jul 22, 2014

Contributor

Quick question: when do you plan on pushing this to rubygems?

This comment has been minimized.

Copy link
@alloy

alloy Jul 22, 2014

Author Member

@jpsim I currently have no ETA for that, I’ll discuss it internally.

This comment has been minimized.

Copy link
@jpsim

jpsim Jul 22, 2014

Contributor

Cool, thanks.

result
end

Expand Down
62 changes: 58 additions & 4 deletions spec/project/object/file_reference_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,67 @@ module ProjectSpecs
@file.last_known_file_type.should.be.nil
end

it "returns whether it is a proxy" do
@file.proxy?.should == false
end

it "can have associated comments, but these are no longer used by Xcode" do
@file.comments = 'This file was automatically generated.'
@file.comments.should == 'This file was automatically generated.'
end

describe "concerning proxies" do
it "returns that it is not a proxy" do
@file.should.not.be.a.proxy
end

it "returns no proxies" do
@file.file_reference_proxies.should.be.empty
end

before do
@file_container = @project.new(PBXContainerItemProxy)
@file_container.container_portal = @file.uuid
@file_proxy = @project.new(PBXReferenceProxy)
@project.main_group.children << @file_proxy
@file_proxy.remote_ref = @file_container

@target_container = @project.new(PBXContainerItemProxy)
@target_container.container_portal = @file.uuid
@target_dependency = @project.new(PBXTargetDependency)
@target = @project.new_target(:static_library, 'Pods', :ios)
@target.dependencies << @target_dependency
@target_dependency.target_proxy = @target_container

@group = @project.main_group.new_group('Products')
@project_reference = Xcodeproj::Project::ObjectDictionary.new(@project.root_object.references_by_keys_attributes.first, @project.root_object)
@project_reference['ProjectRef'] = @file
@project_reference['ProductGroup'] = @group
@project.root_object.project_references << @project_reference
end

it "returns the project reference metadata" do
@file.project_reference_metadata.should == @project_reference
end

it "returns the proxy containers that are contained in this external project" do
@file.proxy_containers.should == [@file_container, @target_container]
end

it "returns the file reference proxies" do
@file.file_reference_proxies.should == [@file_proxy]
end

it "returns the target dependencies that depend on targets in this Xcode project file reference" do
@file.target_dependency_proxies.should == [@target_dependency]
end

it "removes the proxy related objects when removing the file reference" do
@file.remove_from_project
@project.objects_by_uuid[@file_proxy.uuid].should == nil
@project.objects_by_uuid[@file_container.uuid].should == nil
@project.objects_by_uuid[@target_container.uuid].should == nil
@project.objects_by_uuid[@target_dependency.uuid].should == nil
@project.objects_by_uuid[@group.uuid].should == nil
@project.root_object.project_references.should.not.include @project_reference
@target.dependencies.should.be.empty
end
end
end
end
35 changes: 35 additions & 0 deletions spec/project_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,41 @@ module ProjectSpecs
new_instance.should == @project
end

it "can save a project after removing a subproject" do
project = Xcodeproj::Project.open(fixture_path("Sample Project/Cocoa Application.xcodeproj"))

# UUID's related to ReferencedProject.xcodeproj (subproject)
# FIXME: these UUID's should all be deleted automatically when calling

This comment has been minimized.

Copy link
@kylef

kylef Jul 21, 2014

Contributor

I though you made it remove these already?

This comment has been minimized.

Copy link
@fabiopelosin

fabiopelosin Jul 21, 2014

Member

👍

This comment has been minimized.

Copy link
@alloy

alloy Jul 22, 2014

Author Member

@kylef Oops, thanks! Fixed in b2fdba8.

# remove_from_project on the subproject PBXFileReference
# See https://github.com/CocoaPods/Xcodeproj/issues/158
uuids_to_remove = [
"E5FBB3451635ED35009E96B0", # The Xcode subproject file reference that should trigger the removal.

"5138059B16499F4C001D82AD", # PBXContainerItemProxy links to E5FBB3451635ED35009E96B0
"5138059C16499F4C001D82AD", # PBXTargetDependency links to 5138059B16499F4C001D82AD
"E5FBB3461635ED35009E96B0", # PBXGroup for products links to E5FBB34C1635ED36009E96B0, E5FBB34E1635ED36009E96B0, E5FBB3501635ED36009E96B0

"E5FBB34B1635ED36009E96B0", # PBXContainerItemProxy links to E5FBB3451635ED35009E96B0
"E5FBB34C1635ED36009E96B0", # PBXReferenceProxy links to E5FBB34B1635ED36009E96B0

"E5FBB34D1635ED36009E96B0", # PBXContainerItemProxy links to E5FBB3451635ED35009E96B0
"E5FBB34E1635ED36009E96B0", # PBXReferenceProxy links to E5FBB34D1635ED36009E96B0

"E5FBB34F1635ED36009E96B0", # PBXContainerItemProxy links to E5FBB3451635ED35009E96B0
"E5FBB3501635ED36009E96B0", # PBXReferenceProxy links to E5FBB34F1635ED36009E96B0
]

subproject_file_reference = project.objects_by_uuid['E5FBB3451635ED35009E96B0']
subproject_file_reference.remove_from_project
project.save(@path)

new_instance = Xcodeproj::Project.open(@path)
new_instance.objects.count.should > 0 # make sure we still have a valid project
new_instance.root_object.project_references.should.be.empty # this contains the Products group of the external project
removed_objects = new_instance.objects.select { |o| uuids_to_remove.include?(o.uuid) }
removed_objects.count.should == 0
end

it "can open a project and save it without altering any information" do
project = Xcodeproj::Project.open(fixture_path("Sample Project/Cocoa Application.xcodeproj"))
plist = Xcodeproj.read_plist(fixture_path("Sample Project/Cocoa Application.xcodeproj/project.pbxproj"))
Expand Down

0 comments on commit 9ce8ac2

Please sign in to comment.