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
@@ -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