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
@@ -0,0 +1,146 @@
1
+ module Xcodeproj
2
+
3
+ # This modules groups all the constants known to Xcodeproj.
4
+ #
5
+ module Constants
6
+
7
+ # The last known archive version to Xcodeproj.
8
+ #
9
+ LAST_KNOWN_ARCHIVE_VERSION = 1
10
+
11
+ # The last known object version to Xcodeproj.
12
+ #
13
+ LAST_KNOWN_OBJECT_VERSION = 46
14
+
15
+ # The all the known ISAs grouped by superclass.
16
+ #
17
+ KNOWN_ISAS = {
18
+ 'AbstractObject' => %w[
19
+ PBXBuildFile
20
+ AbstractBuildPhase
21
+ PBXBuildRule
22
+ XCBuildConfiguration
23
+ XCConfigurationList
24
+ PBXContainerItemProxy
25
+ PBXFileReference
26
+ PBXGroup
27
+ PBXNativeTarget
28
+ PBXProject
29
+ PBXTargetDependency
30
+ ],
31
+
32
+ 'AbstractBuildPhase' => %w[
33
+ PBXCopyFilesBuildPhase
34
+ PBXResourcesBuildPhase
35
+ PBXSourcesBuildPhase
36
+ PBXFrameworksBuildPhase
37
+ PBXHeadersBuildPhase
38
+ PBXShellScriptBuildPhase
39
+ ],
40
+
41
+ 'PBXGroup' => %w[
42
+ XCVersionGroup
43
+ PBXVariantGroup
44
+ ]
45
+ }.freeze
46
+
47
+ # The list of the super classes for each ISA.
48
+ #
49
+ ISAS_SUPER_CLASSES = %w[ AbstractObject AbstractBuildPhase PBXGroup ]
50
+
51
+ # The known file types corresponding to each extension.
52
+ #
53
+ FILE_TYPES_BY_EXTENSION = {
54
+ 'a' => 'archive.ar',
55
+ 'application' => 'wrapper.application',
56
+ 'dylib' => 'compiled.mach-o.dylib',
57
+ 'framework' => 'wrapper.framework',
58
+ 'h' => 'sourcecode.c.h',
59
+ 'm' => 'sourcecode.c.objc',
60
+ 'xcconfig' => 'text.xcconfig',
61
+ 'xcdatamodel' => 'wrapper.xcdatamodel',
62
+ }.freeze
63
+
64
+ # The uniform type identifier of various product types.
65
+ #
66
+ PRODUCT_TYPE_UTI = {
67
+ :application => 'com.apple.product-type.application',
68
+ :dynamic_library => 'com.apple.product-type.library.dynamic',
69
+ :static_library => 'com.apple.product-type.library.static',
70
+ }.freeze
71
+
72
+ # The common build settings grouped by platform, and build configuration
73
+ # name.
74
+ #
75
+ COMMON_BUILD_SETTINGS = {
76
+ :all => {
77
+ 'GCC_VERSION' => 'com.apple.compilers.llvm.clang.1_0',
78
+ 'GCC_PRECOMPILE_PREFIX_HEADER' => 'YES',
79
+ 'PRODUCT_NAME' => '$(TARGET_NAME)',
80
+ 'SKIP_INSTALL' => 'YES',
81
+ 'DSTROOT' => '/tmp/xcodeproj.dst',
82
+ 'ALWAYS_SEARCH_USER_PATHS' => 'NO',
83
+ 'GCC_C_LANGUAGE_STANDARD' => 'gnu99',
84
+ 'INSTALL_PATH' => "$(BUILT_PRODUCTS_DIR)",
85
+ 'OTHER_LDFLAGS' => '',
86
+ 'COPY_PHASE_STRIP' => 'YES',
87
+ }.freeze,
88
+ :debug => {
89
+ 'GCC_DYNAMIC_NO_PIC' => 'NO',
90
+ 'GCC_PREPROCESSOR_DEFINITIONS' => ["DEBUG=1", "$(inherited)"],
91
+ 'GCC_SYMBOLS_PRIVATE_EXTERN' => 'NO',
92
+ 'GCC_OPTIMIZATION_LEVEL' => '0',
93
+ 'COPY_PHASE_STRIP' => 'NO',
94
+ }.freeze,
95
+ :release => {
96
+ # Empty?
97
+ }.freeze,
98
+ :ios => {
99
+ 'ARCHS' => "$(ARCHS_STANDARD_32_BIT)",
100
+ 'IPHONEOS_DEPLOYMENT_TARGET' => '4.3',
101
+ 'PUBLIC_HEADERS_FOLDER_PATH' => "$(TARGET_NAME)",
102
+ 'SDKROOT' => 'iphoneos',
103
+ }.freeze,
104
+ :osx => {
105
+ 'ARCHS' => "$(ARCHS_STANDARD_64_BIT)",
106
+ 'GCC_ENABLE_OBJC_EXCEPTIONS' => 'YES',
107
+ 'GCC_VERSION' => 'com.apple.compilers.llvm.clang.1_0',
108
+ 'MACOSX_DEPLOYMENT_TARGET' => '10.7',
109
+ 'SDKROOT' => 'macosx',
110
+ 'COMBINE_HIDPI_IMAGES' => 'YES',
111
+ }.freeze,
112
+ [:osx, :debug] => {
113
+ 'ONLY_ACTIVE_ARCH' => 'YES',
114
+ }.freeze,
115
+ [:osx, :release] => {
116
+ 'DEBUG_INFORMATION_FORMAT' => 'dwarf-with-dsym',
117
+ }.freeze,
118
+ [:ios, :debug] => {
119
+ # Empty?
120
+ }.freeze,
121
+ [:ios, :release] => {
122
+ 'VALIDATE_PRODUCT' => 'YES',
123
+ }.freeze,
124
+ }.freeze
125
+
126
+ # The corresponding numeric value of each copy build phase destination.
127
+ #
128
+ COPY_FILES_BUILD_PHASE_DESTINATIONS = {
129
+ :absolute_path => '0',
130
+ :products_directory => '16',
131
+ :wrapper => '1',
132
+ :resources => '7', #default
133
+ :executables => '6',
134
+ :java_resources => '15',
135
+ :frameworks => '10',
136
+ :shared_frameworks => '11',
137
+ :shared_support => '12',
138
+ :plug_ins => '13'
139
+ }.freeze
140
+
141
+ # The extensions which are associated with header files
142
+ #
143
+ HEADER_FILES_EXTENSIONS = %w| .h .hh .hpp |.freeze
144
+
145
+ end
146
+ end
@@ -0,0 +1,28 @@
1
+ module Xcodeproj
2
+ module Helper
3
+ class TargetDiff
4
+ attr_reader :project, :target1, :target2
5
+
6
+ def initialize(project, target1_name, target2_name)
7
+ @project = project
8
+ unless @target1 = @project.targets.find { |target| target.name == target1_name }
9
+ raise ArgumentError, "Target 1 by name `#{target1_name}' not found in the project."
10
+ end
11
+ unless @target2 = @project.targets.find { |target| target.name == target2_name }
12
+ raise ArgumentError, "Target 1 by name `#{target2_name}' not found in the project."
13
+ end
14
+ end
15
+
16
+ # @return [Array<PBXBuildFile>] A list of source files (that will be
17
+ # compiled) which are in ‘target 2’ but not in ‘target 1’. The list is
18
+ # sorted by file path.
19
+ def new_source_build_files
20
+ @target2.source_build_phase.files.reject do |target2_build_file|
21
+ @target1.source_build_phase.files.any? do |target1_build_file|
22
+ target1_build_file.file_ref.path == target2_build_file.file_ref.path
23
+ end
24
+ end.sort_by { |build_file| build_file.file_ref.path }
25
+ end
26
+ end
27
+ end
28
+ end
@@ -3,185 +3,398 @@ require 'pathname'
3
3
  require 'xcodeproj/xcodeproj_ext'
4
4
 
5
5
  require 'xcodeproj/project/object'
6
+ require 'xcodeproj/project/recursive_diff'
6
7
 
7
8
  module Xcodeproj
9
+
8
10
  # This class represents a Xcode project document.
9
11
  #
10
12
  # It can be used to manipulate existing documents or even create new ones
11
13
  # from scratch.
12
14
  #
13
- # The Project API returns instances of AbstractPBXObject which wrap the objects
14
- # described in the Xcode project document.
15
+ # An Xcode project document is a plist file where the root is a dictionary
16
+ # containing the following keys:
17
+ #
18
+ # - archiveVersion: the version of the document.
19
+ # - objectVersion: the version of the objects description.
20
+ # - classes: a key that apparently is always empty.
21
+ # - objects: a dictionary where the UUID of every object is associated to
22
+ # its attributes.
23
+ # - rootObject: the UUID identifier of the root object ({PBXProject}).
24
+ #
25
+ # Every object is in turn a dictionary that specifies an `isa` (the class of
26
+ # the object) and in accordance to it maintains a set attributes. Those
27
+ # attributes might reference one or more other objects by UUID. If the
28
+ # reference is a collection, it is ordered.
29
+ #
30
+ # The {Project} API returns instances of {AbstractObject} which wrap the
31
+ # objects described in the Xcode project document. All the attributes types
32
+ # are preserved from the plist, except for the relationships which are
33
+ # replaced with objects instead of UUIDs.
34
+ #
35
+ # An object might be referenced by multiple objects, an when no other object
36
+ # is references it, it becomes unreachable (the root object is referenced by
37
+ # the project itself). Xcodeproj takes care of adding and removing those
38
+ # objects from the `objects` dictionary so the project is always in a
39
+ # consistent state.
40
+ #
15
41
  class Project
16
- module Object
17
- class PBXProject < AbstractPBXObject
18
- has_many :targets, :class => PBXNativeTarget
19
- has_one :products_group, :uuid => :product_ref_group, :class => PBXGroup
20
- has_one :build_configuration_list, :class => XCConfigurationList
21
- end
22
- end
23
42
 
24
43
  include Object
25
44
 
26
- # Opens a Xcode project document if a path to one is given, otherwise a new
27
- # Project is created.
45
+ # @return [String] the archive version.
46
+ #
47
+ attr_reader :archive_version
48
+
49
+ # @return [Hash] an dictionary whose purpose is unknown.
50
+ #
51
+ attr_reader :classes
52
+
53
+ # @return [String] the objects version.
28
54
  #
29
- # @param [Pathname, String] xcodeproj The path to the Xcode project
30
- # document (xcodeproj).
55
+ attr_reader :object_version
56
+
57
+ # @return [Hash{String => AbstractObject}] A hash containing all the
58
+ # objects of the project by UUID.
59
+ #
60
+ attr_reader :objects_by_uuid
61
+
62
+ # @return [PBXProject] the root object of the project.
63
+ #
64
+ attr_reader :root_object
65
+
66
+ # Creates a new Project instance or initializes one with the data of an
67
+ # existing Xcode document.
68
+ #
69
+ # @param [Pathname, String] xcodeproj
70
+ # The path to the Xcode project document (xcodeproj).
71
+ #
72
+ # @raise If the project versions are more recent than the ones know to
73
+ # Xcodeproj to prevent it from corrupting existing projects. Naturally,
74
+ # this would never happen with a project generated by xcodeproj itself.
75
+ #
76
+ # @raise If it can't find the root object. This means that the project is
77
+ # malformed.
78
+ #
79
+ # @example Opening a project
80
+ # Project.new("path/to/Project.xcodeproj")
31
81
  #
32
- # @return [Project] A new Project instance or one with
33
- # the data of an existing Xcode
34
- # document.
35
82
  def initialize(xcodeproj = nil)
83
+ @objects_by_uuid = {}
84
+ @generated_uuids = []
85
+ @available_uuids = []
86
+
36
87
  if xcodeproj
37
88
  file = File.join(xcodeproj, 'project.pbxproj')
38
- @plist = Xcodeproj.read_plist(file.to_s)
89
+ plist = Xcodeproj.read_plist(file.to_s)
90
+
91
+ @archive_version = plist['archiveVersion']
92
+ @object_version = plist['objectVersion']
93
+ @classes = plist['classes']
94
+
95
+ root_object_uuid = plist['rootObject']
96
+ @root_object = new_from_plist(root_object_uuid, plist['objects'], self)
97
+
98
+ if (@archive_version.to_i > Constants::LAST_KNOWN_ARCHIVE_VERSION || @object_version.to_i > Constants::LAST_KNOWN_OBJECT_VERSION)
99
+ raise '[Xcodeproj] Unknown archive or object version.'
100
+ end
101
+
102
+ unless @root_object
103
+ raise "[Xcodeproj] Unable to find a root object in #{file}."
104
+ end
39
105
  else
40
- @plist = {
41
- 'archiveVersion' => '1',
42
- 'classes' => {},
43
- 'objectVersion' => '46',
44
- 'objects' => {}
45
- }
46
- main_group = groups.new
47
- self.root_object = objects.add(PBXProject, {
48
- 'attributes' => { 'LastUpgradeCheck' => '0450' },
49
- 'compatibilityVersion' => 'Xcode 3.2',
50
- 'developmentRegion' => 'English',
51
- 'hasScannedForEncodings' => '0',
52
- 'knownRegions' => ['en'],
53
- 'mainGroup' => main_group.uuid,
54
- 'productRefGroup' => main_group.groups.new('name' => 'Products').uuid,
55
- 'projectDirPath' => '',
56
- 'projectRoot' => '',
57
- 'targets' => []
58
- })
59
-
60
- config_list = objects.add(XCConfigurationList)
106
+ @archive_version = Constants::LAST_KNOWN_ARCHIVE_VERSION.to_s
107
+ @object_version = Constants::LAST_KNOWN_OBJECT_VERSION.to_s
108
+ @classes = {}
109
+
110
+ root_object = new(PBXProject)
111
+ root_object.main_group = new(PBXGroup)
112
+ root_object.product_ref_group = root_object.main_group.new_group('Products')
113
+
114
+ config_list = new(XCConfigurationList)
61
115
  config_list.default_configuration_name = 'Release'
62
116
  config_list.default_configuration_is_visible = '0'
63
- config_list.build_configurations.new('name' => 'Debug')
64
- config_list.build_configurations.new('name' => 'Release')
65
- self.root_object.build_configuration_list = config_list
117
+ root_object.build_configuration_list = config_list
118
+
119
+ %w| Release Debug |.each do |name|
120
+ build_configuration = new(XCBuildConfiguration)
121
+ build_configuration.name = name
122
+ build_configuration.build_settings = {}
123
+ config_list.build_configurations << build_configuration
124
+ end
66
125
 
67
- # TODO make this work
68
- #self.root_object.product_reference = groups.new('name' => 'Products').uuid
126
+ @root_object = root_object
127
+ root_object.add_referrer(self)
128
+ new_group('Frameworks')
69
129
  end
70
130
  end
71
131
 
72
- # @return [Hash] The internal data.
132
+ # Compares the project to another one, or to a plist representation.
133
+ #
134
+ # @param [#to_hash] other the object to compare.
135
+ #
136
+ # @return [Boolean] whether the project is equivalent to the given object.
137
+ #
138
+ def ==(other)
139
+ other.respond_to?(:to_hash) && to_hash == other.to_hash
140
+ end
141
+
142
+ def to_s
143
+ "Project with root object UUID: #{root_object.uuid}"
144
+ end
145
+
146
+ alias :inspect :to_s
147
+
148
+
149
+
150
+ # @!group Plist serialization
151
+
152
+ # Creates a new object from the given UUID and `objects` hash (of a plist).
153
+ #
154
+ # The method sets up any relationship of the new object, generating the
155
+ # destination object(s) if not already present in the project.
156
+ #
157
+ # @note This method is used to generate the root object
158
+ # from a plist. Subsequent invocation are called by the
159
+ # {AbstractObject#configure_with_plist}. Clients of {Xcodeproj} are
160
+ # not expected to call this method.
161
+ #
162
+ # @visibility private.
163
+ #
164
+ # @param [String] uuid
165
+ # the UUID of the object that needs to be generated.
166
+ #
167
+ # @param [Hash {String => Hash}] objects_by_uuid_plist
168
+ # the `objects` hash of the plist representation of the project.
169
+ #
170
+ # @param [Boolean] root_object
171
+ # whether the requested object is the root object and needs to be
172
+ # retained by the project before configuration to add it to the `objects`
173
+ # hash and avoid infinite loops.
174
+ #
175
+ # @return [AbstractObject] the new object.
176
+ #
177
+ def new_from_plist(uuid, objects_by_uuid_plist, root_object = false)
178
+ attributes = objects_by_uuid_plist[uuid]
179
+ klass = Object.const_get(attributes['isa'])
180
+ object = klass.new(self, uuid)
181
+ object.add_referrer(self) if root_object
182
+
183
+ object.configure_with_plist(objects_by_uuid_plist)
184
+ object
185
+ end
186
+
187
+ # @return [Hash] The plist representation of the project.
188
+ #
73
189
  def to_hash
74
- @plist
190
+ plist = {}
191
+ objects_dictionary = {}
192
+ objects.each { |obj| objects_dictionary[obj.uuid] = obj.to_plist }
193
+ plist['objects'] = objects_dictionary
194
+ plist['archiveVersion'] = archive_version.to_s
195
+ plist['objectVersion'] = object_version.to_s
196
+ plist['classes'] = classes
197
+ plist['rootObject'] = root_object.uuid
198
+ plist
75
199
  end
76
200
 
77
- def ==(other)
78
- other.respond_to?(:to_hash) && @plist == other.to_hash
201
+ alias :to_plist :to_hash
202
+
203
+ # Converts the objects tree to a hash substituting the hash
204
+ # of the referenced to their uuid reference. As a consequene the hash of an
205
+ # object might appear multiple times and the information about their
206
+ # uniqueness is lost.
207
+ #
208
+ # This method is designed to work in conjuction with {Hash#recursive_diff}
209
+ # to provie a complete, yet redable, diff of two projects *not* affected by
210
+ # isa differences.
211
+ #
212
+ # @return [Hash] a hash reppresentation of the project different from the
213
+ # plist one.
214
+ #
215
+ def to_tree_hash
216
+ hash = {}
217
+ objects_dictionary = {}
218
+ hash['objects'] = objects_dictionary
219
+ hash['archiveVersion'] = archive_version.to_s
220
+ hash['objectVersion'] = object_version.to_s
221
+ hash['classes'] = classes
222
+ hash['rootObject'] = root_object.to_tree_hash
223
+ hash
79
224
  end
80
225
 
81
- # This gives access to the objects part of the internal data hash. It is,
82
- # however, **not** recommended to use this to add a hash for an object, for
83
- # that see `add_object_hash`.
226
+ # Serializes the internal data as a property list and stores it on disk at
227
+ # the given path (`xcodeproj` file).
228
+ #
229
+ # @example Saving a project
230
+ # project.save_as("path/to/Project.xcodeproj") #=> true
84
231
  #
85
- # @return [Hash] The `objects` part of the internal data.
86
- def objects_hash
87
- @plist['objects']
232
+ # @param [String, Pathname] projpath The path where the data should be
233
+ # stored.
234
+ #
235
+ # @return [Boolean] Whether or not saving was successful.
236
+ #
237
+ def save_as(projpath)
238
+ projpath = projpath.to_s
239
+ FileUtils.mkdir_p(projpath)
240
+ Xcodeproj.write_plist(to_plist, File.join(projpath, 'project.pbxproj'))
88
241
  end
89
242
 
90
- # This is the preferred way to add an object attributes hash to the objects
91
- # hash, as it validates the data before inserting it.
243
+
244
+
245
+ # @!group Creating objects
246
+
247
+ # Creates a new object with a suitable UUID.
92
248
  #
93
- # @param [String] uuid The UUID of the object.
94
- # @param [Hash] attributes The attributes of the object.
249
+ # The object is only configured with the default values of the `:simple`
250
+ # attributes, for this reason it is better to use the convenience methods
251
+ # offered by the {AbstractObject} subclasses or by this class.
95
252
  #
96
- # @raise [ArgumentError] Raised if the value of the `isa` key is equal
97
- # to `AbstractPBXObject`.
253
+ # @param [Class] klass The concrete subclass of AbstractObject for new
254
+ # object.
98
255
  #
99
- # @todo Ideally we would do more validation here, but I don't think we know
100
- # of all classes that can exist yet.
101
- def add_object_hash(uuid, attributes)
102
- if attributes['isa'] !~ /^(PBX|XC)/
103
- raise ArgumentError, "Attempted to insert a `#{attributes['isa']}' instance into the objects hash, which is not allowed."
104
- end
105
- objects_hash[uuid] = attributes
256
+ # @return [AbstractObject] the new object.
257
+ #
258
+ def new(klass)
259
+ object = klass.new(self, generate_uuid)
260
+ object.initialize_defaults
261
+ object
106
262
  end
107
263
 
108
- # @return [PBXProject] The root object of the project.
109
- def root_object
110
- objects[@plist['rootObject']]
264
+ # Generates a UUID unique for the project.
265
+ #
266
+ # @note UUIDs are not guaranteed to be generated unique because we need to
267
+ # trim the ones generated in the xcodeproj extension.
268
+ #
269
+ # @note Implementation detail: as objects usually are created serially this
270
+ # method creates a batch of UUID and stores the not colliding ones,
271
+ # so the search for collisions with known UUIDS (a performance
272
+ # bottleneck) is performed is performed less often.
273
+ #
274
+ # @return [String] A UUID unique to the project.
275
+ #
276
+ def generate_uuid
277
+ while @available_uuids.empty?
278
+ generate_available_uuid_list
279
+ end
280
+ @available_uuids.shift
111
281
  end
112
282
 
113
- # @param [PBXProject] object The object to assign as the root object.
114
- def root_object=(object)
115
- @plist['rootObject'] = object.uuid
283
+ # @return [Array<String>] the list of all the generated UUIDs.
284
+ #
285
+ # Used for checking new UUIDs for duplicates with UUIDs already generated
286
+ # but used for objects which are not yet part of the `objects` hash but
287
+ # which might be added at a later time.
288
+ #
289
+ attr_reader :generated_uuids
290
+
291
+ # Pre-generates the given number of UUIDs. Useful for optimizing
292
+ # performance when the rough number of objects that will be created is
293
+ # known in advance.
294
+ #
295
+ # @param [Integer] count
296
+ # the number of UUIDs that should be generated.
297
+ #
298
+ # @note This method might generated a minor number of uniques UUIDs than
299
+ # the given count, because some might be duplicated a thus will be
300
+ # discarded.
301
+ #
302
+ # @return [void]
303
+ #
304
+ def generate_available_uuid_list(count = 100)
305
+ new_uuids = (0..count).map { Xcodeproj.generate_uuid }
306
+ uniques = (new_uuids - (@generated_uuids + uuids))
307
+ @generated_uuids += uniques
308
+ @available_uuids += uniques
116
309
  end
117
310
 
118
- # @return [PBXObjectList<AbstractPBXObject>] A list of all the objects in the
119
- # project.
311
+ ## CONVENIENCE METHODS #####################################################
312
+
313
+ # @!group Convenience accessors
314
+
315
+ # @return [Array<AbstractObject>] all the objects of the project.
316
+ #
120
317
  def objects
121
- PBXObjectList.new(AbstractPBXObject, self) do |list|
122
- list.let(:uuid_scope) { objects_hash.keys }
123
- end
318
+ objects_by_uuid.values
124
319
  end
125
320
 
126
- # @return [PBXObjectList<PBXGroup>] A list of all the groups in the
127
- # project.
128
- def groups
129
- objects.list_by_class(PBXGroup)
321
+ # @return [Array<String>] all the UUIDs of the project.
322
+ #
323
+ def uuids
324
+ objects_by_uuid.keys
130
325
  end
131
326
 
132
- # Tries to find a group with the given name.
327
+ # @return [Array<AbstractObject>] all the objects of the project with a
328
+ # given isa.
133
329
  #
134
- # @param [String] name The name of the group to find.
135
- # @return [PBXGroup, nil] The PBXgroup, if found.
136
- def group(name)
137
- groups.object_named(name)
330
+ def list_by_class(klass)
331
+ objects.select { |o| o.class == klass }
138
332
  end
139
333
 
140
- # @return [PBXGroup] The main top-level group.
334
+ # @return [PBXGroup] the main top-level group.
335
+ #
141
336
  def main_group
142
- objects[root_object.attributes['mainGroup']]
337
+ root_object.main_group
143
338
  end
144
339
 
145
- # @return [PBXObjectList<PBXFileReference>] A list of all the files in the
146
- # project.
147
- def files
148
- objects.list_by_class(PBXFileReference)
340
+ # @return [ObjectList<PBXGroup>] a list of all the groups in the
341
+ # project.
342
+ #
343
+ def groups
344
+ main_group.groups
149
345
  end
150
346
 
151
- # Adds a file reference for a system framework to the project.
152
- #
153
- # The file reference can then be added to the buildFiles of a
154
- # PBXFrameworksBuildPhase.
347
+ # Returns a group at the given subpath relative to the main group.
155
348
  #
156
349
  # @example
350
+ # frameworks = project['Frameworks']
351
+ # frameworks.name #=> 'Frameworks'
352
+ # main_group.children.include? frameworks #=> True
157
353
  #
158
- # framework = project.add_system_framework('QuartzCore')
354
+ # @param [String] group_path
159
355
  #
160
- # target = project.targets.first
161
- # build_phase = target.frameworks_build_phases.first
162
- # build_phase.files << framework.buildFiles.new
356
+ # @return [PBXGroup] the group at the given subpath.
163
357
  #
164
- # @todo Make it possible to do: `build_phase << framework`
358
+ def [](group_path)
359
+ main_group[group_path]
360
+ end
361
+
362
+ # @return [ObjectList<PBXFileReference>] a list of all the files in the
363
+ # project.
165
364
  #
166
- # @param [String] name The name of a framework in the SDK System
167
- # directory.
168
- # @return [PBXFileReference] The file reference object.
169
- def add_system_framework(name)
170
- path = "System/Library/Frameworks/#{name}.framework"
171
- if file = files.where(:path => path)
172
- file
173
- else
174
- group = groups.where('name' => 'Frameworks') || groups.new('name' => 'Frameworks')
175
- group.files.new({
176
- 'name' => "#{name}.framework",
177
- 'path' => path,
178
- 'sourceTree' => 'SDKROOT'
179
- })
180
- end
365
+ def files
366
+ objects.select { |obj| obj.class == PBXFileReference }
367
+ end
368
+
369
+ # @return [ObjectList<PBXNativeTarget>] A list of all the targets in the
370
+ # project.
371
+ #
372
+ def targets
373
+ root_object.targets
181
374
  end
182
375
 
183
- # @return [PBXObjectList<XCBuildConfiguration>] A list of project wide
184
- # build configurations.
376
+ # @return [PBXGroup] The group which holds the product file references.
377
+ #
378
+ def products_group
379
+ root_object.product_ref_group
380
+ end
381
+
382
+ # @return [ObjectList<PBXFileReference>] A list of the product file
383
+ # references.
384
+ #
385
+ def products
386
+ products_group.children
387
+ end
388
+
389
+ # @return [PBXGroup] the `Frameworks` group creating it if necessary.
390
+ #
391
+ def frameworks_group
392
+ main_group['Frameworks'] || new_group('Frameworks')
393
+ end
394
+
395
+ # @return [ObjectList<XCBuildConfiguration>] A list of project wide
396
+ # build configurations.
397
+ #
185
398
  def build_configurations
186
399
  root_object.build_configuration_list.build_configurations
187
400
  end
@@ -190,63 +403,156 @@ module Xcodeproj
190
403
  #
191
404
  # @return [Hash] The build settings of the project wide build
192
405
  # configuration with the given name.
406
+ #
193
407
  def build_settings(name)
194
408
  root_object.build_configuration_list.build_settings(name)
195
409
  end
196
410
 
197
- # @todo There are probably other target types too. E.g. an aggregate.
411
+
412
+
413
+ # @!group Convenience methods for generating objects
414
+
415
+ # Creates a new file reference at the given subpath of the main group.
198
416
  #
199
- # @return [PBXObjectList<PBXNativeTarget>] A list of all the targets in
200
- # the project.
201
- def targets
202
- # Better to check the project object for targets to ensure they are
203
- # actually there so the project will work
204
- root_object.targets
417
+ # @param (see PBXGroup#new_file)
418
+ #
419
+ # @return [PBXFileReference] the new file.
420
+ #
421
+ def new_file(path, sub_group_path = nil)
422
+ main_group.new_file(path, sub_group_path)
205
423
  end
206
424
 
207
- # @return [PBXGroup] The group which holds the product file references.
208
- def products_group
209
- root_object.products_group
425
+ # Creates a new group at the given subpath of the main group.
426
+ #
427
+ # @param (see PBXGroup#new_group)
428
+ #
429
+ # @return [PBXGroup] the new group.
430
+ #
431
+ def new_group(name, path = nil)
432
+ main_group.new_group(name, path)
210
433
  end
211
434
 
212
- # @return [PBXObjectList<PBXFileReference>] A list of the product file
213
- # references.
214
- def products
215
- products_group.children
435
+ # Adds a file reference for a system framework to the project.
436
+ #
437
+ # The file reference can then be added to the build files of a
438
+ # {PBXFrameworksBuildPhase}.
439
+ #
440
+ # @example
441
+ #
442
+ # framework = project.add_system_framework('QuartzCore')
443
+ #
444
+ # target = project.targets.first
445
+ # build_phase = target.frameworks_build_phases.first
446
+ # build_phase.files << framework.buildFiles.new
447
+ #
448
+ # @param [String] name The name of a framework in the SDK System
449
+ # directory.
450
+ # @return [PBXFileReference] The file reference object.
451
+ #
452
+ def add_system_framework(name)
453
+ path = "System/Library/Frameworks/#{name}.framework"
454
+ if file = frameworks_group.files.first { |f| f.path == path }
455
+ file
456
+ else
457
+ framework_ref = frameworks_group.new_file(path)
458
+ framework_ref.name = "#{name}.framework"
459
+ framework_ref.source_tree = 'SDKROOT'
460
+ framework_ref
461
+ end
216
462
  end
217
463
 
218
- # @private
219
- IGNORE_GROUPS = ['Frameworks', 'Products', 'Supporting Files']
464
+ # @return [PBXNativeTarget] Creates a new target and adds it to the
465
+ # project.
466
+ #
467
+ # The target is configured for the given platform and its file reference
468
+ # it is added to the {products_group}.
469
+ #
470
+ # The target is pre-populated with common build phases, and all the
471
+ # Frameworks of the project are added to to its Frameworks phase.
472
+ #
473
+ # @todo Adding all the Frameworks is required by CocoaPods and should be
474
+ # performed there.
475
+ #
476
+ # @param [Symbol] type
477
+ # the type of target.
478
+ # Can be `:application`, `:dynamic_library` or `:static_library`.
479
+ #
480
+ # @param [String] name
481
+ # the name of the static library product.
482
+ #
483
+ # @param [Symbol] platform
484
+ # the platform of the static library.
485
+ # Can be `:ios` or `:osx`.
486
+ #
487
+ def new_target(type, name, platform)
488
+ add_system_framework(platform == :ios ? 'Foundation' : 'Cocoa')
489
+
490
+ # Target
491
+ target = new(PBXNativeTarget)
492
+ targets << target
493
+ target.name = name
494
+ target.product_name = name
495
+ target.product_type = Constants::PRODUCT_TYPE_UTI[type]
496
+ target.build_configuration_list = configuration_list(platform)
497
+
498
+ # Product
499
+ product = products_group.new_static_library(name)
500
+ target.product_reference = product
220
501
 
221
- # @todo I think this is here because of easier testing in CocoaPods. Move
222
- # this extension to the CocoaPods specs.
502
+ # Build phases
503
+ target.build_phases << new(PBXSourcesBuildPhase)
504
+ frameworks_phase = new(PBXFrameworksBuildPhase)
505
+ frameworks_group.files.each { |framework| frameworks_phase.add_file_reference(framework) }
506
+ target.build_phases << frameworks_phase
507
+
508
+ target
509
+ end
510
+
511
+ # Returns a new configuration list, populated with release and debug
512
+ # configurations with common build settings for the given platform.
223
513
  #
224
- # @return [Hash] A list of all the groups and their source files.
225
- def source_files
226
- source_files = {}
227
- groups.each do |group|
228
- next if group.name.nil? || IGNORE_GROUPS.include?(group.name)
229
- source_files[group.name] = group.source_files.map(&:pathname)
230
- end
231
- source_files
514
+ # @param [Symbol] platform
515
+ # the platform for the configuration list, can be `:ios` or `:osx`.
516
+ #
517
+ # @return [XCConfigurationList] the generated configuration list.
518
+ #
519
+ def configuration_list(platform)
520
+ cl = new(XCConfigurationList)
521
+ cl.default_configuration_is_visible = '0'
522
+ cl.default_configuration_name = 'Release'
523
+
524
+ release_conf = new(XCBuildConfiguration)
525
+ release_conf.name = 'Release'
526
+ release_conf.build_settings = configuration_list_settings(platform, :release)
527
+
528
+ debug_conf = new(XCBuildConfiguration)
529
+ debug_conf.name = 'Debug'
530
+ debug_conf.build_settings = configuration_list_settings(platform, :debug)
531
+
532
+ cl.build_configurations << release_conf
533
+ cl.build_configurations << debug_conf
534
+ cl
232
535
  end
233
536
 
234
- # Serializes the internal data as a property list and stores it on disk at
235
- # the given path.
537
+ # Returns the common build settings for a given platform and configuration
538
+ # name.
236
539
  #
237
- # @example
540
+ # @param [Symbol] platform
541
+ # the platform for the build settings, can be `:ios` or `:osx`.
238
542
  #
239
- # project.save_as("path/to/Project.xcodeproj") # => true
543
+ # @param [Symbol] name
544
+ # the name of the build configuration, can be `:release` or `:debug`.
240
545
  #
241
- # @param [String, Pathname] projpath The path where the data should be
242
- # stored.
546
+ # @return [Hash] The common build settings
243
547
  #
244
- # @return [true, false] Returns whether or not saving was
245
- # successful.
246
- def save_as(projpath)
247
- projpath = projpath.to_s
248
- FileUtils.mkdir_p(projpath)
249
- Xcodeproj.write_plist(@plist, File.join(projpath, 'project.pbxproj'))
548
+ def configuration_list_settings(platform, name)
549
+ common_settings = Constants::COMMON_BUILD_SETTINGS
550
+ bs = common_settings[:all].dup
551
+ bs = bs.merge(common_settings[name])
552
+ bs = bs.merge(common_settings[platform])
553
+ bs = bs.merge(common_settings[[platform, name]])
554
+ bs
250
555
  end
556
+
251
557
  end
252
558
  end