-
Notifications
You must be signed in to change notification settings - Fork 2.6k
/
file_references_installer.rb
329 lines (291 loc) · 13.1 KB
/
file_references_installer.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
module Pod
class Installer
class Xcode
class PodsProjectGenerator
# Controller class responsible of installing the file references of the
# specifications in the Pods project.
#
class FileReferencesInstaller
# Regex for extracting the region portion of a localized file path. Ex. `Resources/en.lproj` --> `en`
LOCALIZATION_REGION_FILEPATTERN_REGEX = /(\/|^)(?<region>[^\/]*?)\.lproj(\/|$)/
# @return [Sandbox] The sandbox of the installation.
#
attr_reader :sandbox
# @return [Array<PodTarget>] The pod targets of the installation.
#
attr_reader :pod_targets
# @return [Project] The project to install the file references into.
#
attr_reader :pods_project
# @return [Boolean] add support for preserving the file structure of externally sourced pods, in addition to local pods.
#
attr_reader :preserve_pod_file_structure
# Initialize a new instance
#
# @param [Sandbox] sandbox @see #sandbox
# @param [Array<PodTarget>] pod_targets @see #pod_targets
# @param [Project] pods_project @see #pods_project
# @param [Boolean] preserve_pod_file_structure @see #preserve_pod_file_structure
#
def initialize(sandbox, pod_targets, pods_project, preserve_pod_file_structure = false)
@sandbox = sandbox
@pod_targets = pod_targets
@pods_project = pods_project
@preserve_pod_file_structure = preserve_pod_file_structure
end
# Installs the file references.
#
# @return [void]
#
def install!
refresh_file_accessors
prepare_pod_groups
add_source_files_references
add_frameworks_bundles
add_vendored_libraries
add_resources
add_developer_files unless sandbox.development_pods.empty?
link_headers
end
#-----------------------------------------------------------------------#
private
# @!group Installation Steps
# Reads the file accessors contents from the file system.
#
# @note The contents of the file accessors are modified by the clean
# step of the #{PodSourceInstaller} and by the pre install hooks.
#
# @return [void]
#
def refresh_file_accessors
file_accessors.reject do |file_accessor|
pod_name = file_accessor.spec.name
sandbox.local?(pod_name)
end.map(&:path_list).uniq.each(&:read_file_system)
end
# Prepares the main groups to which all files will be added for the respective target
#
def prepare_pod_groups
file_accessors.each do |file_accessor|
pod_name = file_accessor.spec.name
next unless sandbox.local?(pod_name)
root_name = Specification.root_name(pod_name)
path = file_accessor.root
group = pods_project.group_for_spec(root_name)
group.set_path(path) unless group.path == path
end
end
# Adds the source files of the Pods to the Pods project.
#
# @note The source files are grouped by Pod and in turn by subspec
# (recursively).
#
# @return [void]
#
def add_source_files_references
UI.message '- Adding source files' do
add_file_accessors_paths_to_pods_group(:source_files, nil, true)
end
end
# Adds the bundled frameworks to the Pods project
#
# @return [void]
#
def add_frameworks_bundles
UI.message '- Adding frameworks' do
add_file_accessors_paths_to_pods_group(:vendored_frameworks, :frameworks)
end
end
# Adds the bundled libraries to the Pods project
#
# @return [void]
#
def add_vendored_libraries
UI.message '- Adding libraries' do
add_file_accessors_paths_to_pods_group(:vendored_libraries, :frameworks)
end
end
# Adds the resources of the Pods to the Pods project.
#
# @note The source files are grouped by Pod and in turn by subspec
# (recursively) in the resources group.
#
# @return [void]
#
def add_resources
UI.message '- Adding resources' do
refs = add_file_accessors_paths_to_pods_group(:resources, :resources, true)
refs.concat add_file_accessors_paths_to_pods_group(:resource_bundle_files, :resources, true)
add_known_regions(refs)
end
end
def add_developer_files
UI.message '- Adding development pod helper files' do
file_accessors.each do |file_accessor|
pod_name = file_accessor.spec.name
next unless sandbox.local?(pod_name)
root_name = Specification.root_name(pod_name)
paths = file_accessor.developer_files
next if paths.empty?
group = pods_project.group_for_spec(root_name, :developer)
paths.each do |path|
ref = pods_project.add_file_reference(path, group, false)
if path.extname == '.podspec'
pods_project.mark_ruby_file_ref(ref)
end
end
end
end
end
# Creates the link to the headers of the Pod in the sandbox.
#
# @return [void]
#
def link_headers
UI.message '- Linking headers' do
pod_targets.each do |pod_target|
# When integrating Pod as frameworks, built Pods are built into
# frameworks, whose headers are included inside the built
# framework. Those headers do not need to be linked from the
# sandbox.
next if pod_target.build_as_framework? && pod_target.should_build?
pod_target_header_mappings = pod_target.header_mappings_by_file_accessor.values
pod_target_header_mappings.each do |header_mappings|
header_mappings.each do |namespaced_path, files|
pod_target.build_headers.add_files(namespaced_path, files)
end
end
public_header_mappings = pod_target.public_header_mappings_by_file_accessor.values
public_header_mappings.each do |header_mappings|
header_mappings.each do |namespaced_path, files|
sandbox.public_headers.add_files(namespaced_path, files)
end
end
end
end
end
#-----------------------------------------------------------------------#
private
# @!group Private Helpers
# @return [Array<Sandbox::FileAccessor>] The file accessors for all the
# specs platform combinations.
#
def file_accessors
@file_accessors ||= pod_targets.flat_map(&:file_accessors).compact
end
# Adds file references to the list of the paths returned by the file
# accessor with the given key to the given group of the Pods project.
#
# @param [Symbol] file_accessor_key
# The method of the file accessor which would return the list of
# the paths.
#
# @param [Symbol] group_key
# The key of the group of the Pods project.
#
# @param [Boolean] reflect_file_system_structure
# Whether organizing a local pod's files in subgroups inside
# the pod's group is allowed.
#
# @return [Array<PBXFileReference>] the added file references
#
def add_file_accessors_paths_to_pods_group(file_accessor_key, group_key = nil, reflect_file_system_structure = false)
file_accessors.flat_map do |file_accessor|
paths = file_accessor.send(file_accessor_key)
paths = allowable_project_paths(paths)
next [] if paths.empty?
pod_name = file_accessor.spec.name
preserve_pod_file_structure_flag = (sandbox.local?(pod_name) || preserve_pod_file_structure) && reflect_file_system_structure
base_path = preserve_pod_file_structure_flag ? common_path(paths) : nil
actual_group_key = preserve_pod_file_structure_flag ? nil : group_key
group = pods_project.group_for_spec(pod_name, actual_group_key)
paths.map do |path|
pods_project.add_file_reference(path, group, preserve_pod_file_structure_flag, base_path)
end
end
end
# Filters a list of paths down to those paths which can be added to
# the Xcode project. Some paths are intermediates and only their children
# should be added, while some paths are treated as bundles and their
# children should not be added directly.
#
# @param [Array<Pathname>] paths
# The paths to files or directories on disk.
#
# @return [Array<Pathname>] The paths which can be added to the Xcode project
#
def allowable_project_paths(paths)
lproj_paths = Set.new
lproj_paths_with_files = Set.new
allowable_paths = paths.select do |path|
path_str = path.to_s
# We add the directory for a Core Data model, but not the items in it.
next if path_str =~ /.*\.xcdatamodeld\/.+/i
# We add the directory for a Core Data migration mapping, but not the items in it.
next if path_str =~ /.*\.xcmappingmodel\/.+/i
# We add the directory for an asset catalog, but not the items in it.
next if path_str =~ /.*\.xcassets\/.+/i
if path_str =~ /\.lproj(\/|$)/i
# If the element is an .lproj directory then save it and potentially
# add it later if we don't find any contained items.
if path_str =~ /\.lproj$/i && path.directory?
lproj_paths << path
next
end
# Collect the paths for the .lproj directories that contain files.
lproj_path = /(^.*\.lproj)\/.*/i.match(path_str)[1]
lproj_paths_with_files << Pathname(lproj_path)
# Directories nested within an .lproj directory are added as file
# system references so their contained items are not added directly.
next if path.dirname.dirname == lproj_path
end
true
end
# Only add the path for the .lproj directories that do not have anything
# within them added as well. This generally happens if the glob within the
# resources directory was not a recursive glob.
allowable_paths + lproj_paths.subtract(lproj_paths_with_files).to_a
end
# Returns a Pathname of the nearest parent from which all the given paths descend.
# Converts each Pathname to a list of path components and finds the longest common prefix
#
# @param [Array<Pathname>] paths
# The paths to files or directories on disk. Must be absolute paths
#
# @return [Pathname] Pathname of the nearest parent shared by paths, or nil if none exists
#
def common_path(paths)
return nil if paths.empty?
strs = paths.map do |path|
unless path.absolute?
raise ArgumentError, "Paths must be absolute #{path}"
end
path.dirname.to_s
end
min, max = strs.minmax
min = min.split('/')
max = max.split('/')
idx = min.size.times { |i| break i if min[i] != max[i] }
result = Pathname.new(min[0...idx].join('/'))
# Don't consider "/" a common path
return result unless result.to_s == '' || result.to_s == '/'
end
# Adds the known localization regions to the root of the project
#
# @param [Array<PBXFileReferences>] file_references the resource file references
#
def add_known_regions(file_references)
pattern = LOCALIZATION_REGION_FILEPATTERN_REGEX
regions = file_references.map do |ref|
if (match = ref.path.to_s.match(pattern))
match[:region]
end
end.compact
pods_project.root_object.known_regions = (pods_project.root_object.known_regions | regions).sort
end
#-----------------------------------------------------------------------#
end
end
end
end
end