-
Notifications
You must be signed in to change notification settings - Fork 2.6k
/
target_integrator.rb
238 lines (211 loc) · 8.77 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
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
# @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!
return if native_targets.empty?
UI.section(integration_message) do
add_xcconfig_base_configuration
add_pods_library
add_copy_resources_script_phase
add_check_manifest_lock_script_phase
user_project.save
end
end
# @return [Array<PBXNativeTarget>] the user targets for integration.
#
def native_targets
unless @native_targets
target_uuids = target.user_target_uuids
native_targets = target_uuids.map do |uuid|
native_target = user_project.objects_by_uuid[uuid]
unless native_target
raise Informative, "[Bug] Unable to find the target with " \
"the `#{uuid}` UUID for the `#{target}` integration library"
end
native_target
end
non_integrated = 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
@native_targets = non_integrated
end
@native_targets
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
# Read the pods project from the disk to ensure that it is up to date as
# other TargetIntegrators might have modified it.
#
def pods_project
@pods_project ||= Xcodeproj::Project.open(target.sandbox.project_path)
end
# @return [String] a string representation suitable for debugging.
#
def inspect
"#<#{self.class} for target `#{target.label}'>"
end
#---------------------------------------------------------------------#
# @!group Integration steps
private
# @return [Specification::Consumer] the consumer for the specifications.
#
def spec_consumers
@spec_consumers ||= target.pod_targets.map(&:file_accessors).flatten.map(&:spec_consumer)
end
# Adds the `xcconfig` configurations files generated for the current
# {TargetDefinition} to the build configurations of the targets that
# should be integrated.
#
# @note It also checks if any build setting of the build
# configurations overrides the `xcconfig` file and warns the
# user.
#
# @todo If the xcconfig is already set don't override it and inform
# the user.
#
# @return [void]
#
def add_xcconfig_base_configuration
xcconfig = user_project.files.select { |f| f.path == target.xcconfig_relative_path }.first ||
user_project.new_file(target.xcconfig_relative_path)
native_targets.each do |native_target|
check_overridden_build_settings(target.xcconfig, native_target)
native_target.build_configurations.each do |config|
config.base_configuration_reference = xcconfig
end
end
end
# Adds spec libraries to the frameworks build phase of the
# {TargetDefinition} integration libraries. Adds a file reference to
# the library of the {TargetDefinition} and adds it to the frameworks
# build phase of the targets.
#
# @return [void]
#
def add_pods_library
frameworks = user_project.frameworks_group
native_targets.each do |native_target|
library = frameworks.files.select { |f| f.path == target.product_name }.first ||
frameworks.new_product_ref_for_target(target.name, :static_library)
unless native_target.frameworks_build_phase.files_references.include?(library)
native_target.frameworks_build_phase.add_file_reference(library)
end
end
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.each do |native_target|
phase = native_target.shell_script_build_phases.select { |bp| bp.name == phase_name }.first ||
native_target.new_shell_script_build_phase(phase_name)
path = target.copy_resources_script_relative_path
phase.shell_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.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
#---------------------------------------------------------------------#
# @!group Private helpers.
private
# Informs the user about any build setting of the target which might
# override the given xcconfig file.
#
# @return [void]
#
def check_overridden_build_settings(xcconfig, native_target)
return unless xcconfig
configs_by_overridden_key = {}
native_target.build_configurations.each do |config|
xcconfig.attributes.keys.each do |key|
target_value = config.build_settings[key]
if target_value && !target_value.include?('$(inherited)')
configs_by_overridden_key[key] ||= []
configs_by_overridden_key[key] << config.name
end
end
configs_by_overridden_key.each do |key, config_names|
name = "#{native_target.name} [#{config_names.join(' - ')}]"
actions = [
"Use the `$(inherited)` flag, or",
"Remove the build settings from the target."
]
UI.warn("The target `#{name}` overrides the `#{key}` build " \
"setting defined in `#{target.xcconfig_relative_path}'.",
actions)
end
end
end
# @return [String] the message that should be displayed for the target
# integration.
#
def integration_message
"Integrating Pod #{'target'.pluralize(target.pod_targets.size)} " \
"`#{target.pod_targets.map(&:name).to_sentence}` " \
"into aggregate target #{target.name} " \
"of project #{UI.path target.user_project_path}."
end
#---------------------------------------------------------------------#
end
end
end
end