xcodeproj 0.3.5 → 0.4.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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
+