-
Notifications
You must be signed in to change notification settings - Fork 2.6k
/
target_integrator.rb
280 lines (252 loc) · 10.9 KB
/
target_integrator.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
require 'active_support/core_ext/string/inflections'
module Pod
class Installer
class UserProjectIntegrator
# This class is responsible for integrating the library generated by a
# {TargetDefinition} with its destination project.
#
class TargetIntegrator
autoload :XCConfigIntegrator, 'cocoapods/installer/user_project_integrator/target_integrator/xcconfig_integrator'
# @return [Array<Symbol>] the symbol types, which require that the pod
# frameworks are embedded in the output directory / product bundle.
#
EMBED_FRAMEWORK_TARGET_TYPES = [:application, :unit_test_bundle].freeze
# @return [Target] the target that should be integrated.
#
attr_reader :target
# @param [Target] target @see #target_definition
#
def initialize(target)
@target = target
end
# Integrates the user project targets. Only the targets that do **not**
# already have the Pods library in their frameworks build phase are
# processed.
#
# @return [void]
#
def integrate!
UI.section(integration_message) do
# TODO: refactor into Xcodeproj https://github.com/CocoaPods/Xcodeproj/issues/202
project_is_dirty = [
XCConfigIntegrator.integrate(target, native_targets),
update_to_cocoapods_0_34,
remove_embed_frameworks_script_phases,
unless native_targets_to_integrate.empty?
add_pods_library
add_embed_frameworks_script_phase if target.requires_frameworks?
add_copy_resources_script_phase
add_check_manifest_lock_script_phase
true
end,
].any?
if project_is_dirty
user_project.save
else
# There is a bug in Xcode where the process of deleting and
# re-creating the xcconfig files used in the build
# configuration cause building the user project to fail until
# Xcode is relaunched.
#
# Touching/saving the project causes Xcode to reload these.
#
# https://github.com/CocoaPods/CocoaPods/issues/2665
FileUtils.touch(user_project.path + 'project.pbxproj')
end
end
end
# @return [String] a string representation suitable for debugging.
#
def inspect
"#<#{self.class} for target `#{target.label}'>"
end
private
# @!group Integration steps
#---------------------------------------------------------------------#
# Fixes the paths of the copy resource scripts.
#
# @return [Bool] whether any changes to the project were made.
#
# @todo This can be removed for CocoaPods 1.0
#
def update_to_cocoapods_0_34
phases = native_targets.map do |target|
target.shell_script_build_phases.select do |bp|
bp.name == 'Copy Pods Resources'
end
end.flatten
script_path = target.copy_resources_script_relative_path
shell_script = %("#{script_path}"\n)
changes = false
phases.each do |phase|
unless phase.shell_script == shell_script
phase.shell_script = shell_script
changes = true
end
end
changes
end
# Adds spec product reference to the frameworks build phase of the
# {TargetDefinition} integration libraries. Adds a file reference to
# the frameworks group of the project and adds it to the frameworks
# build phase of the targets.
#
# @return [void]
#
def add_pods_library
frameworks = user_project.frameworks_group
native_targets_to_integrate.each do |native_target|
build_phase = native_target.frameworks_build_phase
# Find and delete possible reference for the other product type
old_product_name = target.requires_frameworks? ? target.static_library_name : target.framework_name
old_product_ref = frameworks.files.find { |f| f.path == old_product_name }
if old_product_ref.present?
UI.message("Removing old Pod product reference #{old_product_name} from project.")
build_phase.remove_file_reference(old_product_ref)
frameworks.remove_reference(old_product_ref)
end
# Find or create and add a reference for the current product type
target_basename = target.product_basename
new_product_ref = frameworks.files.find { |f| f.path == target.product_name } ||
frameworks.new_product_ref_for_target(target_basename, target.product_type)
build_file = build_phase.build_file(new_product_ref) ||
build_phase.add_file_reference(new_product_ref)
if target.requires_frameworks?
# Weak link the aggregate target's product, because as it contains
# no symbols, it isn't copied into the app bundle. dyld will so
# never try to find the missing executable at runtime.
build_file.settings ||= {}
build_file.settings['ATTRIBUTES'] = ['Weak']
end
end
end
# Find or create a 'Embed Pods Frameworks' Copy Files Build Phase
#
# @return [void]
#
def add_embed_frameworks_script_phase
phase_name = 'Embed Pods Frameworks'
targets_to_embed_in = native_targets_to_integrate.select do |target|
EMBED_FRAMEWORK_TARGET_TYPES.include?(target.symbol_type)
end
targets_to_embed_in.each do |native_target|
embed_build_phase = native_target.shell_script_build_phases.find { |bp| bp.name == phase_name }
unless embed_build_phase.present?
UI.message("Adding Build Phase '#{phase_name}' to project.")
embed_build_phase = native_target.new_shell_script_build_phase(phase_name)
end
script_path = target.embed_frameworks_script_relative_path
embed_build_phase.shell_script = %("#{script_path}"\n)
embed_build_phase.show_env_vars_in_log = '0'
end
end
# Delete 'Embed Pods Frameworks' Build Phases, if they exist
# and are not needed anymore due to not integrating the
# dependencies by frameworks.
#
# @return [Bool] whether any changes to the project were made.
#
def remove_embed_frameworks_script_phases
return false if target.requires_frameworks?
phase_name = 'Embed Pods Frameworks'
result = false
native_targets.each do |native_target|
embed_build_phase = native_target.shell_script_build_phases.find { |bp| bp.name == phase_name }
next unless embed_build_phase.present?
native_target.build_phases.delete(embed_build_phase)
result = true
end
result
end
# Adds a shell script build phase responsible to copy the resources
# generated by the TargetDefinition to the bundle of the product of the
# targets.
#
# @return [void]
#
def add_copy_resources_script_phase
phase_name = 'Copy Pods Resources'
native_targets_to_integrate.each do |native_target|
phase = native_target.shell_script_build_phases.find { |bp| bp.name == phase_name }
phase ||= native_target.new_shell_script_build_phase(phase_name)
script_path = target.copy_resources_script_relative_path
phase.shell_script = %("#{script_path}"\n)
phase.show_env_vars_in_log = '0'
end
end
# Adds a shell script build phase responsible for checking if the Pods
# locked in the Pods/Manifest.lock file are in sync with the Pods defined
# in the Podfile.lock.
#
# @note The build phase is appended to the front because to fail
# fast.
#
# @return [void]
#
def add_check_manifest_lock_script_phase
phase_name = 'Check Pods Manifest.lock'
native_targets_to_integrate.each do |native_target|
next if native_target.shell_script_build_phases.any? { |phase| phase.name == phase_name }
phase = native_target.project.new(Xcodeproj::Project::Object::PBXShellScriptBuildPhase)
native_target.build_phases.unshift(phase)
phase.name = phase_name
phase.shell_script = <<-EOS.strip_heredoc
diff "${PODS_ROOT}/../Podfile.lock" "${PODS_ROOT}/Manifest.lock" > /dev/null
if [[ $? != 0 ]] ; then
cat << EOM
error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.
EOM
exit 1
fi
EOS
phase.show_env_vars_in_log = '0'
end
end
private
# @!group Private helpers
#---------------------------------------------------------------------#
# @return [Array<PBXNativeTarget>] The list of all the targets that
# match the given target.
#
def native_targets
@native_targets ||= target.user_targets(user_project)
end
# @return [Array<PBXNativeTarget>] The list of the targets
# that have not been integrated by past installations
# of
#
def native_targets_to_integrate
unless @native_targets_to_integrate
@native_targets_to_integrate = native_targets.reject do |native_target|
native_target.frameworks_build_phase.files.any? do |build_file|
file_ref = build_file.file_ref
file_ref &&
file_ref.isa == 'PBXFileReference' &&
file_ref.display_name == target.product_name
end
end
end
@native_targets_to_integrate
end
# Read the project from the disk to ensure that it is up to date as
# other TargetIntegrators might have modified it.
#
def user_project
@user_project ||= Xcodeproj::Project.open(target.user_project_path)
end
# @return [Specification::Consumer] the consumer for the specifications.
#
def spec_consumers
@spec_consumers ||= target.pod_targets.map(&:file_accessors).flatten.map(&:spec_consumer)
end
# @return [String] the message that should be displayed for the target
# integration.
#
def integration_message
"Integrating target `#{target.name}` " \
"(#{UI.path target.user_project_path} project)"
end
end
end
end
end