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.
- checksums.yaml +7 -0
- data/LICENSE +19 -0
- data/README.md +95 -0
- data/bin/xcodeproj +10 -0
- data/lib/xcodeproj/command/config_dump.rb +91 -0
- data/lib/xcodeproj/command/project_diff.rb +56 -0
- data/lib/xcodeproj/command/show.rb +60 -0
- data/lib/xcodeproj/command/sort.rb +44 -0
- data/lib/xcodeproj/command/target_diff.rb +43 -0
- data/lib/xcodeproj/command.rb +63 -0
- data/lib/xcodeproj/config/other_linker_flags_parser.rb +73 -0
- data/lib/xcodeproj/config.rb +386 -0
- data/lib/xcodeproj/constants.rb +465 -0
- data/lib/xcodeproj/differ.rb +239 -0
- data/lib/xcodeproj/gem_version.rb +5 -0
- data/lib/xcodeproj/helper.rb +30 -0
- data/lib/xcodeproj/plist.rb +94 -0
- data/lib/xcodeproj/project/case_converter.rb +90 -0
- data/lib/xcodeproj/project/object/build_configuration.rb +255 -0
- data/lib/xcodeproj/project/object/build_file.rb +84 -0
- data/lib/xcodeproj/project/object/build_phase.rb +369 -0
- data/lib/xcodeproj/project/object/build_rule.rb +109 -0
- data/lib/xcodeproj/project/object/configuration_list.rb +117 -0
- data/lib/xcodeproj/project/object/container_item_proxy.rb +116 -0
- data/lib/xcodeproj/project/object/file_reference.rb +338 -0
- data/lib/xcodeproj/project/object/group.rb +506 -0
- data/lib/xcodeproj/project/object/helpers/build_settings_array_settings_by_object_version.rb +72 -0
- data/lib/xcodeproj/project/object/helpers/file_references_factory.rb +245 -0
- data/lib/xcodeproj/project/object/helpers/groupable_helper.rb +260 -0
- data/lib/xcodeproj/project/object/native_target.rb +751 -0
- data/lib/xcodeproj/project/object/reference_proxy.rb +86 -0
- data/lib/xcodeproj/project/object/root_object.rb +100 -0
- data/lib/xcodeproj/project/object/swift_package_product_dependency.rb +29 -0
- data/lib/xcodeproj/project/object/swift_package_remote_reference.rb +33 -0
- data/lib/xcodeproj/project/object/target_dependency.rb +94 -0
- data/lib/xcodeproj/project/object.rb +534 -0
- data/lib/xcodeproj/project/object_attributes.rb +522 -0
- data/lib/xcodeproj/project/object_dictionary.rb +210 -0
- data/lib/xcodeproj/project/object_list.rb +223 -0
- data/lib/xcodeproj/project/project_helper.rb +341 -0
- data/lib/xcodeproj/project/uuid_generator.rb +132 -0
- data/lib/xcodeproj/project.rb +874 -0
- data/lib/xcodeproj/scheme/abstract_scheme_action.rb +100 -0
- data/lib/xcodeproj/scheme/analyze_action.rb +19 -0
- data/lib/xcodeproj/scheme/archive_action.rb +59 -0
- data/lib/xcodeproj/scheme/build_action.rb +298 -0
- data/lib/xcodeproj/scheme/buildable_product_runnable.rb +55 -0
- data/lib/xcodeproj/scheme/buildable_reference.rb +129 -0
- data/lib/xcodeproj/scheme/command_line_arguments.rb +162 -0
- data/lib/xcodeproj/scheme/environment_variables.rb +170 -0
- data/lib/xcodeproj/scheme/execution_action.rb +86 -0
- data/lib/xcodeproj/scheme/launch_action.rb +179 -0
- data/lib/xcodeproj/scheme/location_scenario_reference.rb +49 -0
- data/lib/xcodeproj/scheme/macro_expansion.rb +34 -0
- data/lib/xcodeproj/scheme/profile_action.rb +57 -0
- data/lib/xcodeproj/scheme/remote_runnable.rb +92 -0
- data/lib/xcodeproj/scheme/send_email_action_content.rb +84 -0
- data/lib/xcodeproj/scheme/shell_script_action_content.rb +77 -0
- data/lib/xcodeproj/scheme/test_action.rb +394 -0
- data/lib/xcodeproj/scheme/xml_element_wrapper.rb +82 -0
- data/lib/xcodeproj/scheme.rb +375 -0
- data/lib/xcodeproj/user_interface.rb +22 -0
- data/lib/xcodeproj/workspace/file_reference.rb +79 -0
- data/lib/xcodeproj/workspace/group_reference.rb +67 -0
- data/lib/xcodeproj/workspace/reference.rb +40 -0
- data/lib/xcodeproj/workspace.rb +277 -0
- data/lib/xcodeproj/xcodebuild_helper.rb +108 -0
- data/lib/xcodeproj.rb +29 -0
- 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
|