xcodeproj 0.3.5 → 0.4.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. data/bin/xcodeproj +13 -0
  2. data/lib/xcodeproj.rb +13 -1
  3. data/lib/xcodeproj/command.rb +131 -0
  4. data/lib/xcodeproj/command/project_diff.rb +53 -0
  5. data/lib/xcodeproj/command/show.rb +35 -0
  6. data/lib/xcodeproj/command/target_diff.rb +43 -0
  7. data/lib/xcodeproj/config.rb +66 -38
  8. data/lib/xcodeproj/constants.rb +146 -0
  9. data/lib/xcodeproj/helper.rb +28 -0
  10. data/lib/xcodeproj/project.rb +462 -156
  11. data/lib/xcodeproj/project/object.rb +251 -138
  12. data/lib/xcodeproj/project/object/build_configuration.rb +27 -0
  13. data/lib/xcodeproj/project/object/build_file.rb +26 -0
  14. data/lib/xcodeproj/project/object/build_phase.rb +132 -61
  15. data/lib/xcodeproj/project/object/build_rule.rb +46 -0
  16. data/lib/xcodeproj/project/object/configuration_list.rb +47 -0
  17. data/lib/xcodeproj/project/object/container_item_proxy.rb +49 -0
  18. data/lib/xcodeproj/project/object/file_reference.rb +80 -48
  19. data/lib/xcodeproj/project/object/group.rb +213 -89
  20. data/lib/xcodeproj/project/object/native_target.rb +171 -114
  21. data/lib/xcodeproj/project/object/root_object.rb +66 -0
  22. data/lib/xcodeproj/project/object/target_dependency.rb +23 -0
  23. data/lib/xcodeproj/project/object_attributes.rb +382 -0
  24. data/lib/xcodeproj/project/object_list.rb +64 -118
  25. data/lib/xcodeproj/project/recursive_diff.rb +116 -0
  26. data/lib/xcodeproj/workspace.rb +56 -2
  27. metadata +38 -10
  28. data/lib/xcodeproj/project/association.rb +0 -54
  29. data/lib/xcodeproj/project/association/has_many.rb +0 -51
  30. data/lib/xcodeproj/project/association/has_one.rb +0 -39
  31. data/lib/xcodeproj/project/association/reflection.rb +0 -86
  32. data/lib/xcodeproj/project/object/configuration.rb +0 -97
@@ -1,207 +1,320 @@
1
- require 'active_support/inflector'
2
-
3
1
  module Xcodeproj
4
2
  class Project
3
+
5
4
  # This is the namespace in which all the classes that wrap the objects in
6
5
  # a Xcode project reside.
7
6
  #
8
- # The base class from which all classes inherit is AbstractPBXObject.
7
+ # The base class from which all classes inherit is AbstractObject.
9
8
  #
10
9
  # If you need to deal with these classes directly, it's possible to include
11
10
  # this namespace into yours, making it unnecessary to prefix them with
12
11
  # Xcodeproj::Project::Object.
13
12
  #
14
13
  # @example
14
+ # class SourceFileSorter
15
+ # include Xcodeproj::Project::Object
16
+ # end
15
17
  #
16
- # class SourceFileSorter
17
- # include Xcodeproj::Project::Object
18
- # end
19
18
  module Object
20
19
 
21
- # Missing constants that begin with either `PBX' or `XC' are assumed to
22
- # be valid classes in a Xcode project. A new AbstractPBXObject subclass is
23
- # created for the constant and returned.
20
+ # @abstract
24
21
  #
25
- # @return [Class] The generated class inhertiting from AbstractPBXObject.
26
- def self.const_missing(name)
27
- if name.to_s =~ /^(PBX|XC)/
28
- klass = Class.new(AbstractPBXObject)
29
- const_set(name, klass)
30
- klass
31
- else
32
- super
33
- end
34
- end
35
-
36
22
  # This is the base class of all object types that can exist in a Xcode
37
23
  # project. As such it provides common behavior, but you can only use
38
- # instances of subclasses of AbstractPBXObject, because this class does
24
+ # instances of subclasses of AbstractObject, because this class does
39
25
  # not exist in actual Xcode projects.
40
- class AbstractPBXObject
26
+ #
27
+ # Almost all the methods implemented by this class are not expected to be
28
+ # used by {Xcodeproj} clients.
29
+ #
30
+ # Subclasses should clearly identify which methods reflect the xcodeproj
31
+ # document model and which methods are offered as convenience. Object
32
+ # lists always represent a relationship to many of the model while simple
33
+ # arrays represent dynamically generated values offered a convenience for
34
+ # clients.
35
+ #
36
+ class AbstractObject
37
+
38
+ # @return [String] the isa of the class.
39
+ #
40
+ def self.isa
41
+ @isa ||= name.split('::').last
42
+ end
41
43
 
42
- # This defines accessor methods for a key-value pair which occurs in the
43
- # attributes hash that the object wraps.
44
+ # @return [String] the object's class name.
44
45
  #
46
+ attr_reader :isa
47
+
48
+ # It is not recommended to instantiate objects through this
49
+ # constructor. To create objects manually is easier to use
50
+ # the {Project#new}. Otherwise, it is possible to use the convenience
51
+ # methods offered by {Xcodeproj} which take care of configuring the
52
+ # objects for common usage cases.
45
53
  #
46
- # @example
54
+ # @param [Project] project
55
+ # the project that will host the object.
47
56
  #
48
- # # Create getter and setter methods named after the key it corresponds to
49
- # # in the attributes hash:
57
+ # @param [String] uuid
58
+ # the UUID of the new object.
50
59
  #
51
- # class PBXBuildPhase < AbstractPBXObject
52
- # attribute :settings
53
- # end
60
+ def initialize(project, uuid)
61
+ @project, @uuid = project, uuid
62
+ @isa = self.class.isa
63
+ @referrers = []
64
+ raise "[Xcodeproj] Attempt to initialize an abstract class." unless @isa.match(/^(PBX|XC)/)
65
+ end
66
+
67
+ # Initializes the object with the default values of simple attributes.
54
68
  #
55
- # build_phase.attributes # => { 'settings' => { 'COMPILER_FLAGS' => '-fobjc-arc' }, ... }
56
- # build_phase.settings # => { 'COMPILER_FLAGS' => '-fobjc-arc' }
69
+ # This method is called by the {Project#new} and is not performed on
70
+ # initialization to prevet adding defaults to objects generated by a
71
+ # plist.
57
72
  #
58
- # build_phase.settings = { 'COMPILER_FLAGS' => '-fobjc-no-arc' }
59
- # build_phase.attributes # => { 'settings' => { 'COMPILER_FLAGS' => '-fobjc-no-arc' }, ... }
73
+ # @return [void]
60
74
  #
61
- # # Or with a custom getter and setter methods:
75
+ def initialize_defaults
76
+ simple_attributes.each { |a| a.set_default(self) }
77
+ end
78
+
79
+ # @return [String] the object universally unique identifier.
62
80
  #
63
- # class PBXCopyFilesBuildPhase < AbstractPBXObject
64
- # attribute :dst_path, :as => :destination_path
65
- # end
81
+ attr_reader :uuid
82
+
83
+ # @return [Project] the project that owns the object.
66
84
  #
67
- # build_phase.attributes # => { 'dstPath' => 'some/path', ... }
68
- # build_phase.destination_path # => 'some/path'
85
+ attr_reader :project
86
+
87
+ # Removes the object from the project by asking to its referrers to
88
+ # remove the reference to it.
69
89
  #
70
- # build_phase.destination_path = 'another/path'
71
- # build_phase.attributes # => { 'dstPath' => 'another/path', ... }
90
+ # @note The root object is owned by the project and should not be
91
+ # manipulated with this method.
72
92
  #
93
+ # @return [void]
73
94
  #
74
- # @param [Symbol, String] attribute_name The key in snake case.
75
- # @option options [Symbol String] :as An optional custom name for
76
- # the getter and setter methods.
77
- def self.attribute(name, options = {})
78
- accessor_name = (options[:as] || name).to_s
79
- attribute_name = name.to_s.camelize(:lower) # change `foo_bar' to `fooBar'
80
- define_method(accessor_name) { @attributes[attribute_name] }
81
- define_method("#{accessor_name}=") { |value| @attributes[attribute_name] = value }
95
+ def remove_from_project
96
+ @project.objects_by_uuid.delete(uuid)
97
+ @referrers.each { |referrer| referrer.remove_reference(self) }
98
+ raise "[Xcodeproj] BUG: #{self} should have no referrers instead the following objects are still referencing it #{referrers}" unless referrers.count == 0
82
99
  end
83
100
 
84
- def self.isa
85
- @isa ||= name.split('::').last
101
+ # Returns the value of the name attribute or returns a generic name for
102
+ # the object.
103
+ #
104
+ # @note Not all concrete classes implement the name attribute and this
105
+ # method prevents from overriding it in plist.
106
+ #
107
+ # @return [String] a name for the object.
108
+ #
109
+ def display_name
110
+ result = name if respond_to?(:name)
111
+ result || isa.gsub(/^(PBX|XC)/, '')
86
112
  end
87
113
 
88
- attr_reader :uuid, :attributes, :project
114
+ # @!group Reference counting
89
115
 
90
- # [String] the object's class name
91
- attribute :isa
92
-
93
- # [String] the object's name
94
- attribute :name
116
+ # @return [Array<ObjectList>] The list of the objects that have a
117
+ # reference to this object.
118
+ #
119
+ attr_reader :referrers
95
120
 
96
- # It is not recommended that you instantiate objects through this
97
- # constructor. It is much easier to use associations to create them.
121
+ # Informs the object that another object is referencing it. If the
122
+ # object had no previous references it is added to the project UUIDs
123
+ # hash.
98
124
  #
99
- # @example
125
+ # @return [void]
100
126
  #
101
- # file_reference = project.files.new('path' => 'path/to/file')
127
+ def add_referrer(referrer)
128
+ @referrers << referrer
129
+ @project.objects_by_uuid[uuid] = self
130
+ end
131
+
132
+ # Informs the object that another object stopped referencing it. If the
133
+ # object has no other references it is removed form project UUIDs hash
134
+ # because it is unreachable.
135
+ #
136
+ # @return [void]
102
137
  #
103
- # @return [AbstractPBXObject]
104
- def initialize(project, uuid, attributes)
105
- @project, @attributes = project, attributes
106
- self.isa ||= self.class.isa
107
- unless uuid
108
- # Add new objects to the main hash with a unique UUID
109
- uuid = generate_uuid
110
- @project.add_object_hash(uuid, @attributes)
138
+ def remove_referrer(referrer)
139
+ @referrers.delete(referrer)
140
+ if @referrers.count == 0
141
+ @project.objects_by_uuid.delete(uuid)
111
142
  end
112
- @uuid = uuid
113
143
  end
114
144
 
115
- def destroy
116
- @project.objects_hash.delete(uuid)
117
- end
145
+ # Removes all the references to a given object.
146
+ #
147
+ # @return [void]
148
+ #
149
+ def remove_reference(object)
150
+ to_one_attributes.each do |attrb|
151
+ value = attrb.get_value(self)
152
+ attrb.set_value(self, nil) if value.equal?(object)
153
+ end
118
154
 
119
- def ==(other)
120
- other.is_a?(AbstractPBXObject) && self.uuid == other.uuid
155
+ to_many_attributes.each do |attrb|
156
+ list = attrb.get_value(self)
157
+ list.delete(object)
158
+ end
121
159
  end
122
160
 
123
- def <=>(other)
124
- self.uuid <=> other.uuid
125
- end
161
+ # @!group Plist related methods
126
162
 
127
- def inspect
128
- "#<#{isa} UUID: `#{uuid}', name: `#{name}'>"
129
- end
163
+ # Configures the object with the objects hash from a plist.
164
+ #
165
+ # **Implementation detail**: it is important that the attributes for a
166
+ # given concrete class are unique because the value is removed from the
167
+ # array at each iteration and duplicate would result in nil values.
168
+ #
169
+ # @return [void]
170
+ #
171
+ def configure_with_plist(objects_by_uuid_plist)
172
+ object_plist = objects_by_uuid_plist[uuid].dup
130
173
 
131
- def matches_attributes?(attributes)
132
- attributes.all? do |attribute, expected_value|
133
- return nil unless respond_to?(attribute)
174
+ raise "[Xcodeproj] Attempt to initialize `#{isa}` from plist with different isa `#{object_plist}`" unless object_plist['isa'] == isa
175
+ object_plist.delete('isa')
134
176
 
135
- if expected_value.is_a?(Hash)
136
- send(attribute).matches_attributes?(expected_value)
137
- else
138
- send(attribute) == expected_value
139
- end
177
+ simple_attributes.each do |attrb|
178
+ attrb.set_value(self, object_plist[attrb.plist_name])
179
+ object_plist.delete(attrb.plist_name)
140
180
  end
141
- end
142
181
 
143
- # Returns a PBXObjectList instance of objects in the `uuid_list`.
144
- #
145
- # By default this list will scope the list by objects matching the
146
- # specified class and add objects, pushed onto the list, to the given
147
- # `uuid_list` array.
148
- #
149
- # If a block is given the list instance is yielded so that the default
150
- # callbacks can be overridden.
151
- #
152
- # @param [Array] uuid_list The UUID array instance which is
153
- # part of the internal data. If this
154
- # would be an arbitrary array and an
155
- # object is added, then it doesn't
156
- # actually modify the internal data,
157
- # meaning the change is lost.
158
- #
159
- # @param [AbstractPBXObject] klass The AbstractPBXObject subclass to
160
- # which the list should be scoped.
161
- #
162
- # @yield [PBXObjectList] The list instance, allowing you to
163
- # easily override the callbacks.
164
- #
165
- # @return [PBXObjectList<klass>] The list of matching objects.
166
- def list_by_class(uuid_list, klass)
167
- PBXObjectList.new(klass, @project) do |list|
168
- list.let(:uuid_scope) do
169
- # TODO why does this not work? should be more efficient.
170
- #uuid_list.select do |uuid|
171
- #@project.objects_hash[uuid]['isa'] == klass.isa
172
- #end
173
- uuid_list.map { |uuid| @project.objects[uuid] }.select { |o| o.is_a?(klass) }.map(&:uuid)
182
+ to_one_attributes.each do |attrb|
183
+ ref_uuid = object_plist[attrb.plist_name]
184
+ if ref_uuid
185
+ ref = project.objects_by_uuid[ref_uuid] || project.new_from_plist(ref_uuid, objects_by_uuid_plist)
186
+ attrb.set_value(self, ref)
174
187
  end
175
- list.let(:push) do |new_object|
176
- # Add the uuid of a newly created object to the uuids list
177
- uuid_list << new_object.uuid
188
+ object_plist.delete(attrb.plist_name)
189
+ end
190
+
191
+ to_many_attributes.each do |attrb|
192
+ ref_uuids = object_plist[attrb.plist_name] || []
193
+ list = attrb.get_value(self)
194
+ ref_uuids.each do |ref_uuid|
195
+ ref = project.objects_by_uuid[ref_uuid] || project.new_from_plist(ref_uuid, objects_by_uuid_plist)
196
+ list << ref
178
197
  end
179
- yield list if block_given?
198
+ object_plist.delete(attrb.plist_name)
199
+ end
200
+ unless object_plist.empty?
201
+ raise "[!] Xcodeproj doesn't know about the following attributes " \
202
+ "#{object_plist} for the '#{isa}' isa.\n" \
203
+ "Please file and issue: https://github.com/CocoaPods/Xcodeproj/issues/new"
180
204
  end
181
205
  end
182
206
 
183
- private
207
+ # @note the key for simple and to_one attributes usually appears only
208
+ # if there is a value. To many keys always appear with an empty
209
+ # array.
210
+ #
211
+ def to_plist
212
+ plist = {}
213
+ plist['isa'] = isa
214
+
215
+ simple_attributes.each do |attrb|
216
+ value = attrb.get_value(self)
217
+ plist[attrb.plist_name] = value if value
218
+ end
219
+
220
+ to_one_attributes.each do |attrb|
221
+ obj = attrb.get_value(self)
222
+ plist[attrb.plist_name] = obj.uuid if obj
223
+ end
184
224
 
185
- # Generate a truly unique UUID. This is to ensure that cutting up the
186
- # UUID in the xcodeproj extension doesn't cause a collision.
187
- def generate_uuid
188
- begin
189
- uuid = Xcodeproj.generate_uuid
190
- end while @project.objects_hash.has_key?(uuid)
191
- uuid
225
+ to_many_attributes.each do |attrb|
226
+ list = attrb.get_value(self)
227
+ plist[attrb.plist_name] = list.uuids
228
+ end
229
+
230
+ plist
192
231
  end
193
- end
232
+ alias :to_hash :to_plist
233
+
234
+ # Returns a cascade reppresentation of the object without UUIDs.
235
+ #
236
+ # This method is designed to work in conjuction with {Hash#recursive_diff}
237
+ # to provie a complete, yet redable, diff of two projects *not* affected by
238
+ # isa differences.
239
+ #
240
+ # @todo current implementation might cause infinite loops.
241
+ #
242
+ # @return [Hash] a hash reppresentation of the project different from the
243
+ # plist one.
244
+ #
245
+ def to_tree_hash
246
+ hash = {}
247
+ hash['displayName'] = display_name if self.respond_to?(:display_name)
248
+ hash['isa'] = isa
249
+
250
+ simple_attributes.each do |attrb|
251
+ value = attrb.get_value(self)
252
+ hash[attrb.plist_name] = value if value
253
+ end
194
254
 
255
+ to_one_attributes.each do |attrb|
256
+ obj = attrb.get_value(self)
257
+ hash[attrb.plist_name] = obj.to_tree_hash if obj
258
+ end
259
+
260
+ to_many_attributes.each do |attrb|
261
+ list = attrb.get_value(self)
262
+ hash[attrb.plist_name] = list.map { |obj| obj.to_tree_hash }
263
+ end
264
+
265
+ hash
266
+ end
267
+
268
+ # !@group Object methods
269
+
270
+ def ==(other)
271
+ other.is_a?(AbstractObject) && self.to_plist == other.to_plist
272
+ end
273
+
274
+ def <=>(other)
275
+ self.uuid <=> other.uuid
276
+ end
277
+
278
+ def inspect
279
+ s = "#<UUID: `#{uuid}', isa: `#{isa}'>"
280
+ s << ", name: `#{name}'" if respond_to?(:name) && name
281
+ s
282
+ end
283
+ end
195
284
  end
196
285
  end
197
286
  end
198
287
 
199
- require 'xcodeproj/project/association'
288
+ require 'xcodeproj/project/object_attributes'
200
289
  require 'xcodeproj/project/object_list'
201
290
 
202
- # Now load the rest of the classes which inherit from AbstractPBXObject.
291
+ # Required because some classes have cyclical references to each other.
292
+ #
293
+ # In ruby 1.8.7 the hash are not sorted so it is necessary to use an array to
294
+ # preserve the proper loading order of the various super classes.
295
+ #
296
+ # @todo I'm sure that there is a method to achieve the same result which
297
+ # doesn't present the risk of some rubist laughing at me :-)
298
+ #
299
+ Xcodeproj::Constants::ISAS_SUPER_CLASSES.each do |superclass_name|
300
+ isas = Xcodeproj::Constants::KNOWN_ISAS[superclass_name]
301
+ superklass = Xcodeproj::Project::Object.const_get(superclass_name)
302
+ isas.each do |isa|
303
+ c = Class.new(superklass)
304
+ Xcodeproj::Project::Object.const_set(isa, c)
305
+ end
306
+ end
307
+
308
+ # Now load the concrete subclasses.
309
+ require 'xcodeproj/project/object/build_configuration'
310
+ require 'xcodeproj/project/object/build_file'
203
311
  require 'xcodeproj/project/object/build_phase'
204
- require 'xcodeproj/project/object/configuration'
312
+ require 'xcodeproj/project/object/build_rule'
313
+ require 'xcodeproj/project/object/configuration_list'
314
+ require 'xcodeproj/project/object/container_item_proxy'
205
315
  require 'xcodeproj/project/object/file_reference'
206
316
  require 'xcodeproj/project/object/group'
207
317
  require 'xcodeproj/project/object/native_target'
318
+ require 'xcodeproj/project/object/root_object'
319
+ require 'xcodeproj/project/object/target_dependency'
320
+