plasmo_xcodeproj 1.21.1

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 (69) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +19 -0
  3. data/README.md +95 -0
  4. data/bin/xcodeproj +10 -0
  5. data/lib/xcodeproj/command/config_dump.rb +91 -0
  6. data/lib/xcodeproj/command/project_diff.rb +56 -0
  7. data/lib/xcodeproj/command/show.rb +60 -0
  8. data/lib/xcodeproj/command/sort.rb +44 -0
  9. data/lib/xcodeproj/command/target_diff.rb +43 -0
  10. data/lib/xcodeproj/command.rb +63 -0
  11. data/lib/xcodeproj/config/other_linker_flags_parser.rb +73 -0
  12. data/lib/xcodeproj/config.rb +386 -0
  13. data/lib/xcodeproj/constants.rb +465 -0
  14. data/lib/xcodeproj/differ.rb +239 -0
  15. data/lib/xcodeproj/gem_version.rb +5 -0
  16. data/lib/xcodeproj/helper.rb +30 -0
  17. data/lib/xcodeproj/plist.rb +94 -0
  18. data/lib/xcodeproj/project/case_converter.rb +90 -0
  19. data/lib/xcodeproj/project/object/build_configuration.rb +255 -0
  20. data/lib/xcodeproj/project/object/build_file.rb +84 -0
  21. data/lib/xcodeproj/project/object/build_phase.rb +369 -0
  22. data/lib/xcodeproj/project/object/build_rule.rb +109 -0
  23. data/lib/xcodeproj/project/object/configuration_list.rb +117 -0
  24. data/lib/xcodeproj/project/object/container_item_proxy.rb +116 -0
  25. data/lib/xcodeproj/project/object/file_reference.rb +338 -0
  26. data/lib/xcodeproj/project/object/group.rb +506 -0
  27. data/lib/xcodeproj/project/object/helpers/build_settings_array_settings_by_object_version.rb +72 -0
  28. data/lib/xcodeproj/project/object/helpers/file_references_factory.rb +245 -0
  29. data/lib/xcodeproj/project/object/helpers/groupable_helper.rb +260 -0
  30. data/lib/xcodeproj/project/object/native_target.rb +751 -0
  31. data/lib/xcodeproj/project/object/reference_proxy.rb +86 -0
  32. data/lib/xcodeproj/project/object/root_object.rb +100 -0
  33. data/lib/xcodeproj/project/object/swift_package_product_dependency.rb +29 -0
  34. data/lib/xcodeproj/project/object/swift_package_remote_reference.rb +33 -0
  35. data/lib/xcodeproj/project/object/target_dependency.rb +94 -0
  36. data/lib/xcodeproj/project/object.rb +534 -0
  37. data/lib/xcodeproj/project/object_attributes.rb +522 -0
  38. data/lib/xcodeproj/project/object_dictionary.rb +210 -0
  39. data/lib/xcodeproj/project/object_list.rb +223 -0
  40. data/lib/xcodeproj/project/project_helper.rb +341 -0
  41. data/lib/xcodeproj/project/uuid_generator.rb +132 -0
  42. data/lib/xcodeproj/project.rb +874 -0
  43. data/lib/xcodeproj/scheme/abstract_scheme_action.rb +100 -0
  44. data/lib/xcodeproj/scheme/analyze_action.rb +19 -0
  45. data/lib/xcodeproj/scheme/archive_action.rb +59 -0
  46. data/lib/xcodeproj/scheme/build_action.rb +298 -0
  47. data/lib/xcodeproj/scheme/buildable_product_runnable.rb +55 -0
  48. data/lib/xcodeproj/scheme/buildable_reference.rb +129 -0
  49. data/lib/xcodeproj/scheme/command_line_arguments.rb +162 -0
  50. data/lib/xcodeproj/scheme/environment_variables.rb +170 -0
  51. data/lib/xcodeproj/scheme/execution_action.rb +86 -0
  52. data/lib/xcodeproj/scheme/launch_action.rb +179 -0
  53. data/lib/xcodeproj/scheme/location_scenario_reference.rb +49 -0
  54. data/lib/xcodeproj/scheme/macro_expansion.rb +34 -0
  55. data/lib/xcodeproj/scheme/profile_action.rb +57 -0
  56. data/lib/xcodeproj/scheme/remote_runnable.rb +92 -0
  57. data/lib/xcodeproj/scheme/send_email_action_content.rb +84 -0
  58. data/lib/xcodeproj/scheme/shell_script_action_content.rb +77 -0
  59. data/lib/xcodeproj/scheme/test_action.rb +394 -0
  60. data/lib/xcodeproj/scheme/xml_element_wrapper.rb +82 -0
  61. data/lib/xcodeproj/scheme.rb +375 -0
  62. data/lib/xcodeproj/user_interface.rb +22 -0
  63. data/lib/xcodeproj/workspace/file_reference.rb +79 -0
  64. data/lib/xcodeproj/workspace/group_reference.rb +67 -0
  65. data/lib/xcodeproj/workspace/reference.rb +40 -0
  66. data/lib/xcodeproj/workspace.rb +277 -0
  67. data/lib/xcodeproj/xcodebuild_helper.rb +108 -0
  68. data/lib/xcodeproj.rb +29 -0
  69. metadata +208 -0
@@ -0,0 +1,874 @@
1
+ # frozen_string_literal: true
2
+ require 'atomos'
3
+ require 'fileutils'
4
+ require 'securerandom'
5
+
6
+ require 'xcodeproj/project/object'
7
+ require 'xcodeproj/project/project_helper'
8
+ require 'xcodeproj/project/uuid_generator'
9
+ require 'xcodeproj/plist'
10
+
11
+ module Xcodeproj
12
+ # This class represents a Xcode project document.
13
+ #
14
+ # It can be used to manipulate existing documents or even create new ones
15
+ # from scratch.
16
+ #
17
+ # An Xcode project document is a plist file where the root is a dictionary
18
+ # containing the following keys:
19
+ #
20
+ # - archiveVersion: the version of the document.
21
+ # - objectVersion: the version of the objects description.
22
+ # - classes: a key that apparently is always empty.
23
+ # - objects: a dictionary where the UUID of every object is associated to
24
+ # its attributes.
25
+ # - rootObject: the UUID identifier of the root object ({PBXProject}).
26
+ #
27
+ # Every object is in turn a dictionary that specifies an `isa` (the class of
28
+ # the object) and in accordance to it maintains a set attributes. Those
29
+ # attributes might reference one or more other objects by UUID. If the
30
+ # reference is a collection, it is ordered.
31
+ #
32
+ # The {Project} API returns instances of {AbstractObject} which wrap the
33
+ # objects described in the Xcode project document. All the attributes types
34
+ # are preserved from the plist, except for the relationships which are
35
+ # replaced with objects instead of UUIDs.
36
+ #
37
+ # An object might be referenced by multiple objects, an when no other object
38
+ # is references it, it becomes unreachable (the root object is referenced by
39
+ # the project itself). Xcodeproj takes care of adding and removing those
40
+ # objects from the `objects` dictionary so the project is always in a
41
+ # consistent state.
42
+ #
43
+ class Project
44
+ include Object
45
+
46
+ # @return [Pathname] the path of the project.
47
+ #
48
+ attr_reader :path
49
+
50
+ # @return [Pathname] the directory of the project
51
+ #
52
+ attr_reader :project_dir
53
+
54
+ # @param [Pathname, String] path @see path
55
+ # The path provided will be expanded to an absolute path.
56
+ # @param [Bool] skip_initialization
57
+ # Wether the project should be initialized from scratch.
58
+ # @param [Int] object_version
59
+ # Object version to use for serialization, defaults to Xcode 3.2 compatible.
60
+ #
61
+ # @example Creating a project
62
+ # Project.new("path/to/Project.xcodeproj")
63
+ #
64
+ # @note When initializing the project, Xcodeproj mimics the Xcode behaviour
65
+ # including the setup of a debug and release configuration. If you want a
66
+ # clean project without any configurations, you should override the
67
+ # `initialize_from_scratch` method to not add these configurations and
68
+ # manually set the object version.
69
+ #
70
+ def initialize(path, skip_initialization = false, object_version = Constants::DEFAULT_OBJECT_VERSION)
71
+ @path = Pathname.new(path).expand_path
72
+ @project_dir = @path.dirname
73
+ @objects_by_uuid = {}
74
+ @generated_uuids = []
75
+ @available_uuids = []
76
+ @dirty = true
77
+ unless skip_initialization.is_a?(TrueClass) || skip_initialization.is_a?(FalseClass)
78
+ raise ArgumentError, '[Xcodeproj] Initialization parameter expected to ' \
79
+ "be a boolean #{skip_initialization}"
80
+ end
81
+ unless skip_initialization
82
+ initialize_from_scratch
83
+ @object_version = object_version.to_s
84
+ unless Constants::COMPATIBILITY_VERSION_BY_OBJECT_VERSION.key?(object_version)
85
+ raise ArgumentError, "[Xcodeproj] Unable to find compatibility version string for object version `#{object_version}`."
86
+ end
87
+ root_object.compatibility_version = Constants::COMPATIBILITY_VERSION_BY_OBJECT_VERSION[object_version]
88
+ end
89
+ end
90
+
91
+ # Opens the project at the given path.
92
+ #
93
+ # @param [Pathname, String] path
94
+ # The path to the Xcode project document (xcodeproj).
95
+ #
96
+ # @raise If the project versions are more recent than the ones know to
97
+ # Xcodeproj to prevent it from corrupting existing projects.
98
+ # Naturally, this would never happen with a project generated by
99
+ # xcodeproj itself.
100
+ #
101
+ # @raise If it can't find the root object. This means that the project is
102
+ # malformed.
103
+ #
104
+ # @example Opening a project
105
+ # Project.open("path/to/Project.xcodeproj")
106
+ #
107
+ def self.open(path)
108
+ path = Pathname.pwd + path
109
+ unless Pathname.new(path).exist?
110
+ raise "[Xcodeproj] Unable to open `#{path}` because it doesn't exist."
111
+ end
112
+ project = new(path, true)
113
+ project.send(:initialize_from_file)
114
+ project
115
+ end
116
+
117
+ # @return [String] the archive version.
118
+ #
119
+ attr_reader :archive_version
120
+
121
+ # @return [Hash] an dictionary whose purpose is unknown.
122
+ #
123
+ attr_reader :classes
124
+
125
+ # @return [String] the objects version.
126
+ #
127
+ attr_reader :object_version
128
+
129
+ # @return [Hash{String => AbstractObject}] A hash containing all the
130
+ # objects of the project by UUID.
131
+ #
132
+ attr_reader :objects_by_uuid
133
+
134
+ # @return [PBXProject] the root object of the project.
135
+ #
136
+ attr_reader :root_object
137
+
138
+ # A fast way to see if two {Project} instances refer to the same projects on
139
+ # disk. Use this over {#eql?} when you do not need to compare the full data.
140
+ #
141
+ # This shallow comparison was chosen as the (common) `==` implementation,
142
+ # because it was too easy to introduce changes into the Xcodeproj code-base
143
+ # that were slower than O(1).
144
+ #
145
+ # @return [Boolean] whether or not the two `Project` instances refer to the
146
+ # same projects on disk, determined solely by {#path} and
147
+ # `root_object.uuid` equality.
148
+ #
149
+ # @todo If ever needed, we could also compare `uuids.sort` instead.
150
+ #
151
+ def ==(other)
152
+ other && path == other.path && root_object.uuid == other.root_object.uuid
153
+ end
154
+
155
+ # Compares the project to another one, or to a plist representation.
156
+ #
157
+ # @note This operation can be extremely expensive, because it converts a
158
+ # `Project` instance to a hash, and should _only_ ever be used to
159
+ # determine wether or not the data contents of two `Project` instances
160
+ # are completely equal.
161
+ #
162
+ # To simply determine wether or not two {Project} instances refer to
163
+ # the same projects on disk, use the {#==} method instead.
164
+ #
165
+ # @param [#to_hash] other the object to compare.
166
+ #
167
+ # @return [Boolean] whether the project is equivalent to the given object.
168
+ #
169
+ def eql?(other)
170
+ other.respond_to?(:to_hash) && to_hash == other.to_hash
171
+ end
172
+
173
+ def to_s
174
+ "#<#{self.class}> path:`#{path}` UUID:`#{root_object.uuid}`"
175
+ end
176
+
177
+ alias_method :inspect, :to_s
178
+
179
+ public
180
+
181
+ # @!group Initialization
182
+ #-------------------------------------------------------------------------#
183
+
184
+ # Initializes the instance from scratch.
185
+ #
186
+ def initialize_from_scratch
187
+ @archive_version = Constants::LAST_KNOWN_ARCHIVE_VERSION.to_s
188
+ @classes = {}
189
+
190
+ root_object.remove_referrer(self) if root_object
191
+ @root_object = new(PBXProject)
192
+ root_object.add_referrer(self)
193
+
194
+ root_object.main_group = new(PBXGroup)
195
+ root_object.product_ref_group = root_object.main_group.new_group('Products')
196
+
197
+ config_list = new(XCConfigurationList)
198
+ root_object.build_configuration_list = config_list
199
+ config_list.default_configuration_name = 'Release'
200
+ config_list.default_configuration_is_visible = '0'
201
+ add_build_configuration('Debug', :debug)
202
+ add_build_configuration('Release', :release)
203
+
204
+ new_group('Frameworks')
205
+ end
206
+
207
+ # Initializes the instance with the project stored in the `path` attribute.
208
+ #
209
+ def initialize_from_file
210
+ pbxproj_path = path + 'project.pbxproj'
211
+ plist = Plist.read_from_path(pbxproj_path.to_s)
212
+ root_object.remove_referrer(self) if root_object
213
+ @root_object = new_from_plist(plist['rootObject'], plist['objects'], self)
214
+ @archive_version = plist['archiveVersion']
215
+ @object_version = plist['objectVersion']
216
+ @classes = plist['classes'] || {}
217
+ @dirty = false
218
+
219
+ unless root_object
220
+ raise "[Xcodeproj] Unable to find a root object in #{pbxproj_path}."
221
+ end
222
+
223
+ if archive_version.to_i > Constants::LAST_KNOWN_ARCHIVE_VERSION
224
+ raise "[Xcodeproj] Unknown archive version (#{archive_version.to_i})."
225
+ end
226
+
227
+ if object_version.to_i > Constants::LAST_KNOWN_OBJECT_VERSION
228
+ raise "[Xcodeproj] Unknown object version (#{object_version.to_i})."
229
+ end
230
+
231
+ # Projects can have product_ref_groups that are not listed in the main_groups["Products"]
232
+ root_object.product_ref_group ||= root_object.main_group['Products'] || root_object.main_group.new_group('Products')
233
+ end
234
+
235
+ public
236
+
237
+ # @!group Plist serialization
238
+ #-------------------------------------------------------------------------#
239
+
240
+ # Creates a new object from the given UUID and `objects` hash (of a plist).
241
+ #
242
+ # The method sets up any relationship of the new object, generating the
243
+ # destination object(s) if not already present in the project.
244
+ #
245
+ # @note This method is used to generate the root object
246
+ # from a plist. Subsequent invocation are called by the
247
+ # {AbstractObject#configure_with_plist}. Clients of {Xcodeproj} are
248
+ # not expected to call this method.
249
+ #
250
+ # @param [String] uuid
251
+ # The UUID of the object that needs to be generated.
252
+ #
253
+ # @param [Hash {String => Hash}] objects_by_uuid_plist
254
+ # The `objects` hash of the plist representation of the project.
255
+ #
256
+ # @param [Boolean] root_object
257
+ # Whether the requested object is the root object and needs to be
258
+ # retained by the project before configuration to add it to the
259
+ # `objects` hash and avoid infinite loops.
260
+ #
261
+ # @return [AbstractObject] the new object.
262
+ #
263
+ # @visibility private.
264
+ #
265
+ def new_from_plist(uuid, objects_by_uuid_plist, root_object = false)
266
+ attributes = objects_by_uuid_plist[uuid]
267
+ if attributes
268
+ klass = Object.const_get(attributes['isa'])
269
+ object = klass.new(self, uuid)
270
+ objects_by_uuid[uuid] = object
271
+ object.add_referrer(self) if root_object
272
+ object.configure_with_plist(objects_by_uuid_plist)
273
+ object
274
+ end
275
+ end
276
+
277
+ # @return [Hash] The hash representation of the project.
278
+ #
279
+ def to_hash
280
+ plist = {}
281
+ objects_dictionary = {}
282
+ objects.each { |obj| objects_dictionary[obj.uuid] = obj.to_hash }
283
+ plist['objects'] = objects_dictionary
284
+ plist['archiveVersion'] = archive_version.to_s
285
+ plist['objectVersion'] = object_version.to_s
286
+ plist['classes'] = classes
287
+ plist['rootObject'] = root_object.uuid
288
+ plist
289
+ end
290
+
291
+ def to_ascii_plist
292
+ plist = {}
293
+ objects_dictionary = {}
294
+ objects
295
+ .sort_by { |o| [o.isa, o.uuid] }
296
+ .each do |obj|
297
+ key = Nanaimo::String.new(obj.uuid, obj.ascii_plist_annotation)
298
+ value = obj.to_ascii_plist.tap { |a| a.annotation = nil }
299
+ objects_dictionary[key] = value
300
+ end
301
+ plist['archiveVersion'] = archive_version.to_s
302
+ plist['classes'] = classes
303
+ plist['objectVersion'] = object_version.to_s
304
+ plist['objects'] = objects_dictionary
305
+ plist['rootObject'] = Nanaimo::String.new(root_object.uuid, root_object.ascii_plist_annotation)
306
+ Nanaimo::Plist.new.tap { |p| p.root_object = plist }
307
+ end
308
+
309
+ # Converts the objects tree to a hash substituting the hash
310
+ # of the referenced to their UUID reference. As a consequence the hash of
311
+ # an object might appear multiple times and the information about their
312
+ # uniqueness is lost.
313
+ #
314
+ # This method is designed to work in conjunction with {Hash#recursive_diff}
315
+ # to provide a complete, yet readable, diff of two projects *not* affected
316
+ # by differences in UUIDs.
317
+ #
318
+ # @return [Hash] a hash representation of the project different from the
319
+ # plist one.
320
+ #
321
+ def to_tree_hash
322
+ hash = {}
323
+ objects_dictionary = {}
324
+ hash['objects'] = objects_dictionary
325
+ hash['archiveVersion'] = archive_version.to_s
326
+ hash['objectVersion'] = object_version.to_s
327
+ hash['classes'] = classes
328
+ hash['rootObject'] = root_object.to_tree_hash
329
+ hash
330
+ end
331
+
332
+ # @return [Hash{String => Hash}] A hash suitable to display the project
333
+ # to the user.
334
+ #
335
+ def pretty_print
336
+ build_configurations = root_object.build_configuration_list.build_configurations
337
+ {
338
+ 'File References' => root_object.main_group.pretty_print.values.first,
339
+ 'Targets' => root_object.targets.map(&:pretty_print),
340
+ 'Build Configurations' => build_configurations.sort_by(&:name).map(&:pretty_print),
341
+ }
342
+ end
343
+
344
+ # Serializes the project in the xcodeproj format using the path provided
345
+ # during initialization or the given path (`xcodeproj` file). If a path is
346
+ # provided file references depending on the root of the project are not
347
+ # updated automatically, thus clients are responsible to perform any needed
348
+ # modification before saving.
349
+ #
350
+ # @param [String, Pathname] path
351
+ # The optional path where the project should be saved.
352
+ #
353
+ # @example Saving a project
354
+ # project.save
355
+ # project.save
356
+ #
357
+ # @return [void]
358
+ #
359
+ def save(save_path = nil)
360
+ save_path ||= path
361
+ @dirty = false if save_path == path
362
+ FileUtils.mkdir_p(save_path)
363
+ file = File.join(save_path, 'project.pbxproj')
364
+ Atomos.atomic_write(file) do |f|
365
+ Nanaimo::Writer::PBXProjWriter.new(to_ascii_plist, :pretty => true, :output => f, :strict => false).write
366
+ end
367
+ end
368
+
369
+ # Marks the project as dirty, that is, modified from what is on disk.
370
+ #
371
+ # @return [void]
372
+ #
373
+ def mark_dirty!
374
+ @dirty = true
375
+ end
376
+
377
+ # @return [Boolean] Whether this project has been modified since read from
378
+ # disk or saved.
379
+ #
380
+ def dirty?
381
+ @dirty == true
382
+ end
383
+
384
+ # Replaces all the UUIDs in the project with deterministic MD5 checksums.
385
+ #
386
+ # @note The current sorting of the project is taken into account when
387
+ # generating the new UUIDs.
388
+ #
389
+ # @note This method should only be used for entirely machine-generated
390
+ # projects, as true UUIDs are useful for tracking changes in the
391
+ # project.
392
+ #
393
+ # @return [void]
394
+ #
395
+ def predictabilize_uuids
396
+ UUIDGenerator.new([self]).generate!
397
+ end
398
+
399
+ # Replaces all the UUIDs in the list of provided projects with deterministic MD5 checksums.
400
+ #
401
+ # @param [Array<Project>] projects
402
+ #
403
+ # @note The current sorting of the project is taken into account when
404
+ # generating the new UUIDs.
405
+ #
406
+ # @note This method should only be used for entirely machine-generated
407
+ # projects, as true UUIDs are useful for tracking changes in the
408
+ # project.
409
+ #
410
+ # @return [void]
411
+ #
412
+ def self.predictabilize_uuids(projects)
413
+ UUIDGenerator.new(projects).generate!
414
+ end
415
+
416
+ public
417
+
418
+ # @!group Creating objects
419
+ #-------------------------------------------------------------------------#
420
+
421
+ # Creates a new object with a suitable UUID.
422
+ #
423
+ # The object is only configured with the default values of the `:simple`
424
+ # attributes, for this reason it is better to use the convenience methods
425
+ # offered by the {AbstractObject} subclasses or by this class.
426
+ #
427
+ # @param [Class, String] klass
428
+ # The concrete subclass of AbstractObject for new object or its
429
+ # ISA.
430
+ #
431
+ # @return [AbstractObject] the new object.
432
+ #
433
+ def new(klass)
434
+ if klass.is_a?(String)
435
+ klass = Object.const_get(klass)
436
+ end
437
+ object = klass.new(self, generate_uuid)
438
+ object.initialize_defaults
439
+ object
440
+ end
441
+
442
+ # Generates a UUID unique for the project.
443
+ #
444
+ # @note UUIDs are not guaranteed to be generated unique because we need
445
+ # to trim the ones generated in the xcodeproj extension.
446
+ #
447
+ # @note Implementation detail: as objects usually are created serially
448
+ # this method creates a batch of UUID and stores the not colliding
449
+ # ones, so the search for collisions with known UUIDS (a
450
+ # performance bottleneck) is performed less often.
451
+ #
452
+ # @return [String] A UUID unique to the project.
453
+ #
454
+ def generate_uuid
455
+ generate_available_uuid_list while @available_uuids.empty?
456
+ @available_uuids.shift
457
+ end
458
+
459
+ # @return [Array<String>] the list of all the generated UUIDs.
460
+ #
461
+ # @note Used for checking new UUIDs for duplicates with UUIDs already
462
+ # generated but used for objects which are not yet part of the
463
+ # `objects` hash but which might be added at a later time.
464
+ #
465
+ attr_reader :generated_uuids
466
+
467
+ # Pre-generates the given number of UUIDs. Useful for optimizing
468
+ # performance when the rough number of objects that will be created is
469
+ # known in advance.
470
+ #
471
+ # @param [Integer] count
472
+ # the number of UUIDs that should be generated.
473
+ #
474
+ # @note This method might generated a minor number of uniques UUIDs than
475
+ # the given count, because some might be duplicated a thus will be
476
+ # discarded.
477
+ #
478
+ # @return [void]
479
+ #
480
+ def generate_available_uuid_list(count = 100)
481
+ new_uuids = (0..count).map { SecureRandom.hex(12).upcase }
482
+ uniques = (new_uuids - (@generated_uuids + uuids))
483
+ @generated_uuids += uniques
484
+ @available_uuids += uniques
485
+ end
486
+
487
+ public
488
+
489
+ # @!group Convenience accessors
490
+ #-------------------------------------------------------------------------#
491
+
492
+ # @return [Array<AbstractObject>] all the objects of the project.
493
+ #
494
+ def objects
495
+ objects_by_uuid.values
496
+ end
497
+
498
+ # @return [Array<String>] all the UUIDs of the project.
499
+ #
500
+ def uuids
501
+ objects_by_uuid.keys
502
+ end
503
+
504
+ # @return [Array<AbstractObject>] all the objects of the project with a
505
+ # given ISA.
506
+ #
507
+ def list_by_class(klass)
508
+ objects.select { |o| o.class == klass }
509
+ end
510
+
511
+ # @return [PBXGroup] the main top-level group.
512
+ #
513
+ def main_group
514
+ root_object.main_group
515
+ end
516
+
517
+ # @return [ObjectList<PBXGroup>] a list of all the groups in the
518
+ # project.
519
+ #
520
+ def groups
521
+ main_group.groups
522
+ end
523
+
524
+ # Returns a group at the given subpath relative to the main group.
525
+ #
526
+ # @example
527
+ # frameworks = project['Frameworks']
528
+ # frameworks.name #=> 'Frameworks'
529
+ # main_group.children.include? frameworks #=> True
530
+ #
531
+ # @param [String] group_path @see MobileCoreServices
532
+ #
533
+ # @return [PBXGroup] the group at the given subpath.
534
+ #
535
+ def [](group_path)
536
+ main_group[group_path]
537
+ end
538
+
539
+ # @return [ObjectList<PBXFileReference>] a list of all the files in the
540
+ # project.
541
+ #
542
+ def files
543
+ objects.grep(PBXFileReference)
544
+ end
545
+
546
+ # Returns the file reference for the given absolute path.
547
+ #
548
+ # @param [#to_s] absolute_path
549
+ # The absolute path of the file whose reference is needed.
550
+ #
551
+ # @return [PBXFileReference] The file reference.
552
+ # @return [Nil] If no file reference could be found.
553
+ #
554
+ def reference_for_path(absolute_path)
555
+ absolute_pathname = Pathname.new(absolute_path)
556
+
557
+ unless absolute_pathname.absolute?
558
+ raise ArgumentError, "Paths must be absolute #{absolute_path}"
559
+ end
560
+
561
+ objects.find do |child|
562
+ child.isa == 'PBXFileReference' && child.real_path == absolute_pathname
563
+ end
564
+ end
565
+
566
+ # @return [ObjectList<AbstractTarget>] A list of all the targets in the
567
+ # project.
568
+ #
569
+ def targets
570
+ root_object.targets
571
+ end
572
+
573
+ # @return [ObjectList<PBXNativeTarget>] A list of all the targets in the
574
+ # project excluding aggregate targets.
575
+ #
576
+ def native_targets
577
+ root_object.targets.grep(PBXNativeTarget)
578
+ end
579
+
580
+ # Checks the native target for any targets in the project
581
+ # that are dependent on the native target and would be
582
+ # embedded in it at build time
583
+ #
584
+ # @param [PBXNativeTarget] native target to check for
585
+ # embedded targets
586
+ #
587
+ #
588
+ # @return [Array<PBXNativeTarget>] A list of all targets that
589
+ # are embedded in the passed in target
590
+ #
591
+ def embedded_targets_in_native_target(native_target)
592
+ native_targets.select do |target|
593
+ host_targets_for_embedded_target(target).any? { |host| host.uuid == native_target.uuid }
594
+ end
595
+ end
596
+
597
+ # Returns the native targets, in which the embedded target is
598
+ # embedded. This works by traversing the targets to find those
599
+ # where the target is a dependency.
600
+ #
601
+ # @param [PBXNativeTarget] native target that might be embedded
602
+ # in another target
603
+ #
604
+ # @return [Array<PBXNativeTarget>] the native targets that host the
605
+ # embedded target
606
+ #
607
+ def host_targets_for_embedded_target(embedded_target)
608
+ native_targets.select do |native_target|
609
+ ((embedded_target.uuid != native_target.uuid) &&
610
+ (native_target.dependencies.map(&:native_target_uuid).include? embedded_target.uuid))
611
+ end
612
+ end
613
+
614
+ # @return [PBXGroup] The group which holds the product file references.
615
+ #
616
+ def products_group
617
+ root_object.product_ref_group
618
+ end
619
+
620
+ # @return [ObjectList<PBXFileReference>] A list of the product file
621
+ # references.
622
+ #
623
+ def products
624
+ products_group.children
625
+ end
626
+
627
+ # @return [PBXGroup] the `Frameworks` group creating it if necessary.
628
+ #
629
+ def frameworks_group
630
+ main_group['Frameworks'] || main_group.new_group('Frameworks')
631
+ end
632
+
633
+ # @return [ObjectList<XCConfigurationList>] The build configuration list of
634
+ # the project.
635
+ #
636
+ def build_configuration_list
637
+ root_object.build_configuration_list
638
+ end
639
+
640
+ # @return [ObjectList<XCBuildConfiguration>] A list of project wide
641
+ # build configurations.
642
+ #
643
+ def build_configurations
644
+ root_object.build_configuration_list.build_configurations
645
+ end
646
+
647
+ # Returns the build settings of the project wide build configuration with
648
+ # the given name.
649
+ #
650
+ # @param [String] name
651
+ # The name of a project wide build configuration.
652
+ #
653
+ # @return [Hash] The build settings.
654
+ #
655
+ def build_settings(name)
656
+ root_object.build_configuration_list.build_settings(name)
657
+ end
658
+
659
+ public
660
+
661
+ # @!group Helpers
662
+ #-------------------------------------------------------------------------#
663
+
664
+ # Creates a new file reference in the main group.
665
+ #
666
+ # @param @see PBXGroup#new_file
667
+ #
668
+ # @return [PBXFileReference] the new file.
669
+ #
670
+ def new_file(path, source_tree = :group)
671
+ main_group.new_file(path, source_tree)
672
+ end
673
+
674
+ # Creates a new group at the given subpath of the main group.
675
+ #
676
+ # @param @see PBXGroup#new_group
677
+ #
678
+ # @return [PBXGroup] the new group.
679
+ #
680
+ def new_group(name, path = nil, source_tree = :group)
681
+ main_group.new_group(name, path, source_tree)
682
+ end
683
+
684
+ # Creates a new target and adds it to the project.
685
+ #
686
+ # The target is configured for the given platform and its file reference it
687
+ # is added to the {products_group}.
688
+ #
689
+ # The target is pre-populated with common build settings, and the
690
+ # appropriate Framework according to the platform is added to to its
691
+ # Frameworks phase.
692
+ #
693
+ # @param [Symbol] type
694
+ # the type of target. Can be `:application`, `:framework`,
695
+ # `:dynamic_library` or `:static_library`.
696
+ #
697
+ # @param [String] name
698
+ # the name of the target product.
699
+ #
700
+ # @param [Symbol] platform
701
+ # the platform of the target. Can be `:ios` or `:osx`.
702
+ #
703
+ # @param [String] deployment_target
704
+ # the deployment target for the platform.
705
+ #
706
+ # @param [PBXGroup] product_group
707
+ # the product group, where to add to a file reference of the
708
+ # created target.
709
+ #
710
+ # @param [Symbol] language
711
+ # the primary language of the target, can be `:objc` or `:swift`.
712
+ #
713
+ # @return [PBXNativeTarget] the target.
714
+ #
715
+ def new_target(type, name, platform, deployment_target = nil, product_group = nil, language = nil, product_basename = nil)
716
+ product_group ||= products_group
717
+ product_basename ||= name
718
+ ProjectHelper.new_target(self, type, name, platform, deployment_target, product_group, language, product_basename)
719
+ end
720
+
721
+ # Creates a new resource bundles target and adds it to the project.
722
+ #
723
+ # The target is configured for the given platform and its file reference it
724
+ # is added to the {products_group}.
725
+ #
726
+ # The target is pre-populated with common build settings
727
+ #
728
+ # @param [String] name
729
+ # the name of the resources bundle.
730
+ #
731
+ # @param [Symbol] platform
732
+ # the platform of the resources bundle. Can be `:ios` or `:osx`.
733
+ #
734
+ # @return [PBXNativeTarget] the target.
735
+ #
736
+ def new_resources_bundle(name, platform, product_group = nil, product_basename = nil)
737
+ product_group ||= products_group
738
+ product_basename ||= name
739
+ ProjectHelper.new_resources_bundle(self, name, platform, product_group, product_basename)
740
+ end
741
+
742
+ # Creates a new target and adds it to the project.
743
+ #
744
+ # The target is configured for the given platform and its file reference it
745
+ # is added to the {products_group}.
746
+ #
747
+ # The target is pre-populated with common build settings, and the
748
+ # appropriate Framework according to the platform is added to to its
749
+ # Frameworks phase.
750
+ #
751
+ # @param [String] name
752
+ # the name of the target.
753
+ #
754
+ # @param [Array<AbstractTarget>] target_dependencies
755
+ # targets, which should be added as dependencies.
756
+ #
757
+ # @param [Symbol] platform
758
+ # the platform of the aggregate target. Can be `:ios` or `:osx`.
759
+ #
760
+ # @param [String] deployment_target
761
+ # the deployment target for the platform.
762
+ #
763
+ # @return [PBXNativeTarget] the target.
764
+ #
765
+ def new_aggregate_target(name, target_dependencies = [], platform = nil, deployment_target = nil)
766
+ ProjectHelper.new_aggregate_target(self, name, platform, deployment_target).tap do |aggregate_target|
767
+ target_dependencies.each do |dep|
768
+ aggregate_target.add_dependency(dep)
769
+ end
770
+ end
771
+ end
772
+
773
+ # Adds a new build configuration to the project and populates its with
774
+ # default settings according to the provided type.
775
+ #
776
+ # @param [String] name
777
+ # The name of the build configuration.
778
+ #
779
+ # @param [Symbol] type
780
+ # The type of the build configuration used to populate the build
781
+ # settings, must be :debug or :release.
782
+ #
783
+ # @return [XCBuildConfiguration] The new build configuration.
784
+ #
785
+ def add_build_configuration(name, type)
786
+ build_configuration_list = root_object.build_configuration_list
787
+ if build_configuration = build_configuration_list[name]
788
+ build_configuration
789
+ else
790
+ build_configuration = new(XCBuildConfiguration)
791
+ build_configuration.name = name
792
+ common_settings = Constants::PROJECT_DEFAULT_BUILD_SETTINGS
793
+ settings = ProjectHelper.deep_dup(common_settings[:all])
794
+ settings.merge!(ProjectHelper.deep_dup(common_settings[type]))
795
+ build_configuration.build_settings = settings
796
+ build_configuration_list.build_configurations << build_configuration
797
+ build_configuration
798
+ end
799
+ end
800
+
801
+ # Sorts the project.
802
+ #
803
+ # @param [Hash] options
804
+ # the sorting options.
805
+ # @option options [Symbol] :groups_position
806
+ # the position of the groups can be either `:above` or `:below`.
807
+ #
808
+ # @return [void]
809
+ #
810
+ def sort(options = nil)
811
+ root_object.sort_recursively(options)
812
+ end
813
+
814
+ public
815
+
816
+ # @!group Schemes
817
+ #-------------------------------------------------------------------------#
818
+
819
+ # Get list of shared schemes in project
820
+ #
821
+ # @param [String] path
822
+ # project path
823
+ #
824
+ # @return [Array]
825
+ #
826
+ def self.schemes(project_path)
827
+ schemes = Dir[File.join(project_path, 'xcshareddata', 'xcschemes', '*.xcscheme')].map do |scheme|
828
+ File.basename(scheme, '.xcscheme')
829
+ end
830
+ schemes << File.basename(project_path, '.xcodeproj') if schemes.empty?
831
+ schemes
832
+ end
833
+
834
+ # Recreates the user schemes of the project from scratch (removes the
835
+ # folder) and optionally hides or shares them.
836
+ #
837
+ # @param [Bool] visible
838
+ # Whether the schemes should be visible or hidden.
839
+ #
840
+ # @param [Bool] shared
841
+ # Whether the schemes should be shared or not.
842
+ #
843
+ # @return [void]
844
+ #
845
+ def recreate_user_schemes(visible = true, shared = false)
846
+ schemes_dir = XCScheme.user_data_dir(path)
847
+ FileUtils.rm_rf(schemes_dir)
848
+ FileUtils.mkdir_p(schemes_dir)
849
+
850
+ xcschememanagement = {}
851
+ xcschememanagement['SchemeUserState'] = {}
852
+ xcschememanagement['SuppressBuildableAutocreation'] = {}
853
+
854
+ targets.each do |target|
855
+ scheme = XCScheme.new
856
+
857
+ test_target = target if target.respond_to?(:test_target_type?) && target.test_target_type?
858
+ launch_target = target.respond_to?(:launchable_target_type?) && target.launchable_target_type?
859
+ scheme.configure_with_targets(target, test_target, :launch_target => launch_target)
860
+
861
+ yield scheme, target if block_given?
862
+ scheme.save_as(path, target.name, shared)
863
+ xcscheme_key = "#{target.name}.xcscheme#{shared ? "_^#shared#^_" : ""}"
864
+ xcschememanagement['SchemeUserState'][xcscheme_key] = {}
865
+ xcschememanagement['SchemeUserState'][xcscheme_key]['isShown'] = visible
866
+ end
867
+
868
+ xcschememanagement_path = schemes_dir + 'xcschememanagement.plist'
869
+ Plist.write_to_path(xcschememanagement, xcschememanagement_path)
870
+ end
871
+
872
+ #-------------------------------------------------------------------------#
873
+ end
874
+ end