cocoapods-dykit 0.5.2 → 0.5.3

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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/lib/pod/command.rb +2 -0
  3. data/lib/pod/command/dyinstall.rb +51 -0
  4. data/lib/pod/command/dyupdate.rb +106 -0
  5. data/lib/pod/command/fmwk.rb +4 -0
  6. data/lib/pod/command/lib/dylint.rb +1 -0
  7. data/lib/pod/gem_version.rb +1 -1
  8. data/lib/pod/installer.rb +715 -0
  9. data/lib/pod/installer/analyzer.rb +934 -0
  10. data/lib/pod/installer/analyzer/analysis_result.rb +57 -0
  11. data/lib/pod/installer/analyzer/locking_dependency_analyzer.rb +95 -0
  12. data/lib/pod/installer/analyzer/pod_variant.rb +68 -0
  13. data/lib/pod/installer/analyzer/pod_variant_set.rb +157 -0
  14. data/lib/pod/installer/analyzer/podfile_dependency_cache.rb +54 -0
  15. data/lib/pod/installer/analyzer/sandbox_analyzer.rb +251 -0
  16. data/lib/pod/installer/analyzer/specs_state.rb +84 -0
  17. data/lib/pod/installer/analyzer/target_inspection_result.rb +45 -0
  18. data/lib/pod/installer/analyzer/target_inspector.rb +254 -0
  19. data/lib/pod/installer/installation_options.rb +158 -0
  20. data/lib/pod/installer/pod_source_installer.rb +214 -0
  21. data/lib/pod/installer/pod_source_preparer.rb +77 -0
  22. data/lib/pod/installer/podfile_validator.rb +139 -0
  23. data/lib/pod/installer/post_install_hooks_context.rb +107 -0
  24. data/lib/pod/installer/pre_install_hooks_context.rb +42 -0
  25. data/lib/pod/installer/source_provider_hooks_context.rb +32 -0
  26. data/lib/pod/installer/user_project_integrator.rb +253 -0
  27. data/lib/pod/installer/user_project_integrator/target_integrator.rb +462 -0
  28. data/lib/pod/installer/user_project_integrator/target_integrator/xcconfig_integrator.rb +146 -0
  29. data/lib/pod/installer/xcode.rb +8 -0
  30. data/lib/pod/installer/xcode/pods_project_generator.rb +353 -0
  31. data/lib/pod/installer/xcode/pods_project_generator/aggregate_target_installer.rb +172 -0
  32. data/lib/pod/installer/xcode/pods_project_generator/file_references_installer.rb +367 -0
  33. data/lib/pod/installer/xcode/pods_project_generator/pod_target_installer.rb +718 -0
  34. data/lib/pod/installer/xcode/pods_project_generator/pod_target_integrator.rb +111 -0
  35. data/lib/pod/installer/xcode/pods_project_generator/target_installer.rb +265 -0
  36. data/lib/pod/installer/xcode/target_validator.rb +141 -0
  37. data/lib/pod/resolver.rb +632 -0
  38. metadata +34 -2
@@ -0,0 +1,42 @@
1
+ module Pod
2
+ class DyInstaller
3
+ # Context object designed to be used with the HooksManager which describes
4
+ # the context of the installer before analysis has been completed.
5
+ #
6
+ class PreInstallHooksContext
7
+ # @return [String] The path to the sandbox root (`Pods` directory).
8
+ #
9
+ attr_accessor :sandbox_root
10
+
11
+ # @return [Podfile] The Podfile for the project.
12
+ #
13
+ attr_accessor :podfile
14
+
15
+ # @return [Sandbox] The Sandbox for the project.
16
+ #
17
+ attr_accessor :sandbox
18
+
19
+ # @return [Lockfile] The Lockfile for the project.
20
+ #
21
+ attr_accessor :lockfile
22
+
23
+ # @param [Sandbox] sandbox see {#sandbox}
24
+ #
25
+ # @param [Podfile] podfile see {#podfile}
26
+ #
27
+ # @param [Lockfile] lockfile see {#lockfile}
28
+ #
29
+ # @return [PreInstallHooksContext] Convenience class method to generate the
30
+ # static context.
31
+ #
32
+ def self.generate(sandbox, podfile, lockfile)
33
+ result = new
34
+ result.podfile = podfile
35
+ result.sandbox = sandbox
36
+ result.sandbox_root = sandbox.root.to_s
37
+ result.lockfile = lockfile
38
+ result
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,32 @@
1
+ module Pod
2
+ class DyInstaller
3
+ # Context object designed to be used with the HooksManager which describes
4
+ # the context of the installer before spec sources have been created
5
+ #
6
+ class SourceProviderHooksContext
7
+ # @return [Array<Source>] The source objects to send to the installer
8
+ #
9
+ attr_reader :sources
10
+
11
+ # @return [SourceProviderHooksContext] Convenience class method to generate the
12
+ # static context.
13
+ #
14
+ def self.generate
15
+ result = new
16
+ result
17
+ end
18
+
19
+ def initialize
20
+ @sources = []
21
+ end
22
+
23
+ # @param [Source] Source object to be added to the installer
24
+ #
25
+ def add_source(source)
26
+ unless source.nil?
27
+ @sources << source
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,253 @@
1
+ require 'xcodeproj/workspace'
2
+ require 'xcodeproj/project'
3
+
4
+ require 'active_support/core_ext/string/inflections'
5
+ require 'active_support/core_ext/array/conversions'
6
+
7
+ module Pod
8
+ class DyInstaller
9
+ # The {UserProjectIntegrator} integrates the libraries generated by
10
+ # TargetDefinitions of the {Podfile} with their correspondent user
11
+ # projects.
12
+ #
13
+ class UserProjectIntegrator
14
+ autoload :TargetIntegrator, File.expand_path('../user_project_integrator/target_integrator', __FILE__)
15
+
16
+ # @return [Podfile] the podfile that should be integrated with the user
17
+ # projects.
18
+ #
19
+ attr_reader :podfile
20
+
21
+ # @return [Project] the pods project which contains the libraries to
22
+ # integrate.
23
+ #
24
+ # attr_reader :pods_project
25
+
26
+ attr_reader :sandbox
27
+
28
+ # @return [Pathname] the path of the installation.
29
+ #
30
+ # @todo This is only used to compute the workspace path in case that it
31
+ # should be inferred by the project. If the workspace should be in
32
+ # the same dir of the project, this could be removed.
33
+ #
34
+ attr_reader :installation_root
35
+
36
+ # @return [Array<AggregateTarget>] the targets represented in the Podfile.
37
+ #
38
+ attr_reader :targets
39
+
40
+ # Init a new UserProjectIntegrator
41
+ #
42
+ # @param [Podfile] podfile @see #podfile
43
+ # @param [Sandbox] sandbox @see #sandbox
44
+ # @param [Pathname] installation_root @see #installation_root
45
+ # @param [Array<AggregateTarget>] targets @see #targets
46
+ #
47
+ # @todo Too many initialization arguments
48
+ #
49
+ def initialize(podfile, sandbox, installation_root, targets)
50
+ @podfile = podfile
51
+ @sandbox = sandbox
52
+ @installation_root = installation_root
53
+ @targets = targets
54
+ end
55
+
56
+ # Integrates the user projects associated with the {TargetDefinitions}
57
+ # with the Pods project and its products.
58
+ #
59
+ # @return [void]
60
+ #
61
+ def integrate!
62
+ create_workspace
63
+ integrate_user_targets
64
+ warn_about_xcconfig_overrides
65
+ save_projects
66
+ end
67
+
68
+ #-----------------------------------------------------------------------#
69
+
70
+ private
71
+
72
+ # @!group Integration steps
73
+
74
+ # Creates and saved the workspace containing the Pods project and the
75
+ # user projects, if needed.
76
+ #
77
+ # @note If the workspace already contains the projects it is not saved
78
+ # to avoid Xcode from displaying the revert dialog: `Do you want to
79
+ # keep the Xcode version or revert to the version on disk?`
80
+ #
81
+ # @return [void]
82
+ #
83
+ def create_workspace
84
+ all_projects = user_project_paths.sort.push(sandbox.project_path).uniq
85
+ file_references = all_projects.map do |path|
86
+ relative_path = path.relative_path_from(workspace_path.dirname).to_s
87
+ Xcodeproj::Workspace::FileReference.new(relative_path, 'group')
88
+ end
89
+
90
+ if workspace_path.exist?
91
+ workspace = Xcodeproj::Workspace.new_from_xcworkspace(workspace_path)
92
+ new_file_references = file_references - workspace.file_references
93
+ unless new_file_references.empty?
94
+ new_file_references.each { |fr| workspace << fr }
95
+ workspace.save_as(workspace_path)
96
+ end
97
+
98
+ else
99
+ UI.notice "Please close any current Xcode sessions and use `#{workspace_path.basename}` for this project from now on."
100
+ workspace = Xcodeproj::Workspace.new(*file_references)
101
+ workspace.save_as(workspace_path)
102
+ end
103
+ end
104
+
105
+ # Integrates the targets of the user projects with the libraries
106
+ # generated from the {Podfile}.
107
+ #
108
+ # @note {TargetDefinition} without dependencies are skipped prevent
109
+ # creating empty libraries for targets definitions which are only
110
+ # wrappers for others.
111
+ #
112
+ # @return [void]
113
+ #
114
+ def integrate_user_targets
115
+ target_integrators = targets_to_integrate.sort_by(&:name).map do |target|
116
+ puts "target --> #{target}"
117
+ TargetIntegrator.new(target)
118
+ end
119
+
120
+ Config.instance.with_changes(:silent => true) do
121
+ deintegrator = Deintegrator.new
122
+ all_project_targets = user_projects.flat_map(&:native_targets).uniq
123
+ all_native_targets = targets_to_integrate.flat_map(&:user_targets).uniq
124
+ targets_to_deintegrate = all_project_targets - all_native_targets
125
+ targets_to_deintegrate.each do |target|
126
+ deintegrator.deintegrate_target(target)
127
+ end
128
+ end
129
+
130
+ target_integrators.each(&:integrate!)
131
+ end
132
+
133
+ # Save all user projects.
134
+ #
135
+ # @return [void]
136
+ #
137
+ def save_projects
138
+ user_projects.each do |project|
139
+ if project.dirty?
140
+ project.save
141
+ else
142
+ # There is a bug in Xcode where the process of deleting and
143
+ # re-creating the xcconfig files used in the build
144
+ # configuration cause building the user project to fail until
145
+ # Xcode is relaunched.
146
+ #
147
+ # Touching/saving the project causes Xcode to reload these.
148
+ #
149
+ # https://github.com/CocoaPods/CocoaPods/issues/2665
150
+ FileUtils.touch(project.path + 'project.pbxproj')
151
+ end
152
+ end
153
+ end
154
+
155
+ IGNORED_KEYS = %w(CODE_SIGN_IDENTITY).freeze
156
+ INHERITED_FLAGS = %w($(inherited) ${inherited}).freeze
157
+
158
+ # Checks whether the settings of the CocoaPods generated xcconfig are
159
+ # overridden by the build configuration of a target and prints a
160
+ # warning to inform the user if needed.
161
+ #
162
+ def warn_about_xcconfig_overrides
163
+ targets.each do |aggregate_target|
164
+ aggregate_target.user_targets.each do |user_target|
165
+ user_target.build_configurations.each do |config|
166
+ xcconfig = aggregate_target.xcconfigs[config.name]
167
+ if xcconfig
168
+ (xcconfig.to_hash.keys - IGNORED_KEYS).each do |key|
169
+ target_values = config.build_settings[key]
170
+ if target_values &&
171
+ !INHERITED_FLAGS.any? { |flag| target_values.include?(flag) }
172
+ print_override_warning(aggregate_target, user_target, config, key)
173
+ end
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end
179
+ end
180
+
181
+ private
182
+
183
+ # @!group Private Helpers
184
+ #-----------------------------------------------------------------------#
185
+
186
+ # @return [Pathname] the path where the workspace containing the Pods
187
+ # project and the user projects should be saved.
188
+ #
189
+ def workspace_path
190
+ if podfile.workspace_path
191
+ declared_path = podfile.workspace_path
192
+ path_with_ext = File.extname(declared_path) == '.xcworkspace' ? declared_path : "#{declared_path}.xcworkspace"
193
+ podfile_dir = File.dirname(podfile.defined_in_file || '')
194
+ absolute_path = File.expand_path(path_with_ext, podfile_dir)
195
+ Pathname.new(absolute_path)
196
+ elsif user_project_paths.count == 1
197
+ project = user_project_paths.first.basename('.xcodeproj')
198
+ installation_root + "#{project}.xcworkspace"
199
+ else
200
+ raise Informative, 'Could not automatically select an Xcode ' \
201
+ "workspace. Specify one in your Podfile like so:\n\n" \
202
+ " workspace 'path/to/Workspace.xcworkspace'\n"
203
+ end
204
+ end
205
+
206
+ # @return [Array<Pathname>] the paths of all the user projects referenced
207
+ # by the target definitions.
208
+ #
209
+ # @note Empty target definitions are ignored.
210
+ #
211
+ def user_project_paths
212
+ targets.map(&:user_project_path).compact.uniq
213
+ end
214
+
215
+ def user_projects
216
+ targets.map(&:user_project).compact.uniq
217
+ end
218
+
219
+ def targets_to_integrate
220
+ targets
221
+ end
222
+
223
+ # Prints a warning informing the user that a build configuration of
224
+ # the integrated target is overriding the CocoaPods build settings.
225
+ #
226
+ # @param [Target::AggregateTarget] aggregate_target
227
+ # The umbrella target.
228
+ #
229
+ # @param [XcodeProj::PBXNativeTarget] user_target
230
+ # The native target.
231
+ #
232
+ # @param [Xcodeproj::XCBuildConfiguration] config
233
+ # The build configuration.
234
+ #
235
+ # @param [String] key
236
+ # The key of the overridden build setting.
237
+ #
238
+ def print_override_warning(aggregate_target, user_target, config, key)
239
+ actions = [
240
+ 'Use the `$(inherited)` flag, or',
241
+ 'Remove the build settings from the target.',
242
+ ]
243
+ message = "The `#{user_target.name} [#{config.name}]` " \
244
+ "target overrides the `#{key}` build setting defined in " \
245
+ "`#{aggregate_target.xcconfig_relative_path(config.name)}'. " \
246
+ 'This can lead to problems with the CocoaPods installation'
247
+ UI.warn(message, actions)
248
+ end
249
+
250
+ #-----------------------------------------------------------------------#
251
+ end
252
+ end
253
+ end
@@ -0,0 +1,462 @@
1
+ require 'active_support/core_ext/string/inflections'
2
+
3
+ module Pod
4
+ class DyInstaller
5
+ class UserProjectIntegrator
6
+ # This class is responsible for integrating the library generated by a
7
+ # {TargetDefinition} with its destination project.
8
+ #
9
+ class TargetIntegrator
10
+ autoload :XCConfigIntegrator, File.expand_path('../target_integrator/xcconfig_integrator', __FILE__)
11
+
12
+ # @return [String] the string to use as prefix for every build phase added to the user project
13
+ #
14
+ BUILD_PHASE_PREFIX = '[CP] '.freeze
15
+
16
+ # @return [String] the string to use as prefix for every build phase declared by the user within a podfile
17
+ # or podspec.
18
+ #
19
+ USER_BUILD_PHASE_PREFIX = '[CP-User] '.freeze
20
+
21
+ # @return [String] the name of the check manifest phase
22
+ #
23
+ CHECK_MANIFEST_PHASE_NAME = 'Check Pods Manifest.lock'.freeze
24
+
25
+ # @return [Array<Symbol>] the symbol types, which require that the pod
26
+ # frameworks are embedded in the output directory / product bundle.
27
+ #
28
+ # @note This does not include :app_extension or :watch_extension because
29
+ # these types must have their frameworks embedded in their host targets.
30
+ # For messages extensions, this only applies if it's embedded in a messages
31
+ # application.
32
+ #
33
+ EMBED_FRAMEWORK_TARGET_TYPES = [:application, :unit_test_bundle, :ui_test_bundle, :watch2_extension, :messages_application].freeze
34
+
35
+ # @return [String] the name of the embed frameworks phase
36
+ #
37
+ EMBED_FRAMEWORK_PHASE_NAME = 'Embed Pods Frameworks'.freeze
38
+
39
+ # @return [String] the name of the copy resources phase
40
+ #
41
+ COPY_PODS_RESOURCES_PHASE_NAME = 'Copy Pods Resources'.freeze
42
+
43
+ # @return [Integer] the maximum number of input and output paths to use for a script phase
44
+ #
45
+ MAX_INPUT_OUTPUT_PATHS = 1000
46
+
47
+ # @return [AggregateTarget] the target that should be integrated.
48
+ #
49
+ attr_reader :target
50
+
51
+ # Init a new TargetIntegrator
52
+ #
53
+ # @param [AggregateTarget] target @see #target
54
+ #
55
+ def initialize(target)
56
+ @target = target
57
+ end
58
+
59
+ class << self
60
+ # Adds a shell script build phase responsible to copy (embed) the frameworks
61
+ # generated by the TargetDefinition to the bundle of the product of the
62
+ # targets.
63
+ #
64
+ # @param [PBXNativeTarget] native_target
65
+ # The native target to add the script phase into.
66
+ #
67
+ # @param [String] script_path
68
+ # The script path to execute as part of this script phase.
69
+ #
70
+ # @param [Array<String>] input_paths
71
+ # The input paths (if any) to include for this script phase.
72
+ #
73
+ # @param [Array<String>] output_paths
74
+ # The output paths (if any) to include for this script phase.
75
+ #
76
+ # @return [void]
77
+ #
78
+ def create_or_update_embed_frameworks_script_phase_to_target(native_target, script_path, input_paths = [], output_paths = [])
79
+ phase = TargetIntegrator.create_or_update_build_phase(native_target, BUILD_PHASE_PREFIX + EMBED_FRAMEWORK_PHASE_NAME)
80
+ phase.shell_script = %("#{script_path}"\n)
81
+ phase.input_paths = input_paths
82
+ phase.output_paths = output_paths
83
+ end
84
+
85
+ # Delete a 'Embed Pods Frameworks' Copy Files Build Phase if present
86
+ #
87
+ # @param [PBXNativeTarget] native_target
88
+ # The native target to remove the script phase from.
89
+ #
90
+ def remove_embed_frameworks_script_phase_from_target(native_target)
91
+ embed_build_phase = native_target.shell_script_build_phases.find { |bp| bp.name && bp.name.end_with?(EMBED_FRAMEWORK_PHASE_NAME) }
92
+ return unless embed_build_phase.present?
93
+ native_target.build_phases.delete(embed_build_phase)
94
+ end
95
+
96
+ # Adds a shell script build phase responsible to copy the resources
97
+ # generated by the TargetDefinition to the bundle of the product of the
98
+ # targets.
99
+ #
100
+ # @param [PBXNativeTarget] native_target
101
+ # The native target to add the script phase into.
102
+ #
103
+ # @param [String] script_path
104
+ # The script path to execute as part of this script phase.
105
+ #
106
+ # @param [Array<String>] input_paths
107
+ # The input paths (if any) to include for this script phase.
108
+ #
109
+ # @param [Array<String>] output_paths
110
+ # The output paths (if any) to include for this script phase.
111
+ #
112
+ # @return [void]
113
+ #
114
+ def create_or_update_copy_resources_script_phase_to_target(native_target, script_path, input_paths = [], output_paths = [])
115
+ phase_name = COPY_PODS_RESOURCES_PHASE_NAME
116
+ phase = TargetIntegrator.create_or_update_build_phase(native_target, BUILD_PHASE_PREFIX + phase_name)
117
+ phase.shell_script = %("#{script_path}"\n)
118
+ phase.input_paths = input_paths
119
+ phase.output_paths = output_paths
120
+ end
121
+
122
+ # Delete a 'Copy Pods Resources' script phase if present
123
+ #
124
+ # @param [PBXNativeTarget] native_target
125
+ # The native target to remove the script phase from.
126
+ #
127
+ def remove_copy_resources_script_phase_from_target(native_target)
128
+ build_phase = native_target.shell_script_build_phases.find { |bp| bp.name && bp.name.end_with?(COPY_PODS_RESOURCES_PHASE_NAME) }
129
+ return unless build_phase.present?
130
+ native_target.build_phases.delete(build_phase)
131
+ end
132
+
133
+ # Creates or update a shell script build phase for the given target.
134
+ #
135
+ # @param [PBXNativeTarget] native_target
136
+ # The native target to add the script phase into.
137
+ #
138
+ # @param [String] phase_name
139
+ # The name of the phase to use.
140
+ #
141
+ # @param [Class] phase_class
142
+ # The class of the phase to use.
143
+ #
144
+ # @return [void]
145
+ #
146
+ def create_or_update_build_phase(native_target, phase_name, phase_class = Xcodeproj::Project::Object::PBXShellScriptBuildPhase)
147
+ build_phases = native_target.build_phases.grep(phase_class)
148
+ build_phases.find { |phase| phase.name && phase.name.end_with?(phase_name) }.tap { |p| p.name = phase_name if p } ||
149
+ native_target.project.new(phase_class).tap do |phase|
150
+ UI.message("Adding Build Phase '#{phase_name}' to project.") do
151
+ phase.name = phase_name
152
+ phase.show_env_vars_in_log = '0'
153
+ native_target.build_phases << phase
154
+ end
155
+ end
156
+ end
157
+
158
+ # Updates all target script phases for the current target, including creating or updating, deleting
159
+ # and re-ordering.
160
+ #
161
+ # @return [void]
162
+ #
163
+ def create_or_update_user_script_phases(script_phases, native_target)
164
+ script_phase_names = script_phases.map { |k| k[:name] }
165
+ # Delete script phases no longer present in the target definition.
166
+ native_target_script_phases = native_target.shell_script_build_phases.select { |bp| !bp.name.nil? && bp.name.start_with?(USER_BUILD_PHASE_PREFIX) }
167
+ native_target_script_phases.each do |script_phase|
168
+ script_phase_name_without_prefix = script_phase.name.sub(USER_BUILD_PHASE_PREFIX, '')
169
+ unless script_phase_names.include?(script_phase_name_without_prefix)
170
+ native_target.build_phases.delete(script_phase)
171
+ end
172
+ end
173
+ # Create or update the ones that are expected to be.
174
+ script_phases.each do |td_script_phase|
175
+ name_with_prefix = USER_BUILD_PHASE_PREFIX + td_script_phase[:name]
176
+ phase = TargetIntegrator.create_or_update_build_phase(native_target, name_with_prefix)
177
+ phase.shell_script = td_script_phase[:script]
178
+ phase.shell_path = td_script_phase[:shell_path] if td_script_phase.key?(:shell_path)
179
+ phase.input_paths = td_script_phase[:input_files] if td_script_phase.key?(:input_files)
180
+ phase.output_paths = td_script_phase[:output_files] if td_script_phase.key?(:output_files)
181
+ phase.show_env_vars_in_log = td_script_phase[:show_env_vars_in_log] ? '1' : '0' if td_script_phase.key?(:show_env_vars_in_log)
182
+
183
+ execution_position = td_script_phase[:execution_position]
184
+ unless execution_position == :any
185
+ compile_build_phase_index = native_target.build_phases.index do |bp|
186
+ bp.is_a?(Xcodeproj::Project::Object::PBXSourcesBuildPhase)
187
+ end
188
+ unless compile_build_phase_index.nil?
189
+ script_phase_index = native_target.build_phases.index do |bp|
190
+ bp.is_a?(Xcodeproj::Project::Object::PBXShellScriptBuildPhase) && !bp.name.nil? && bp.name == name_with_prefix
191
+ end
192
+ if (execution_position == :before_compile && script_phase_index > compile_build_phase_index) ||
193
+ (execution_position == :after_compile && script_phase_index < compile_build_phase_index)
194
+ native_target.build_phases.move_from(script_phase_index, compile_build_phase_index)
195
+ end
196
+ end
197
+ end
198
+ end
199
+ end
200
+
201
+ # Script phases can have a limited number of input and output paths due to each one being exported to `env`.
202
+ # A large number can cause a build failure because of limitations in `env`. See issue
203
+ # https://github.com/CocoaPods/CocoaPods/issues/7362.
204
+ #
205
+ # @param [Array<String>] input_paths
206
+ # The input paths to trim.
207
+ #
208
+ # @param [Array<String>] output_paths
209
+ # The output paths to trim.
210
+ #
211
+ # @return [void]
212
+ #
213
+ def validate_input_output_path_limit(input_paths, output_paths)
214
+ if (input_paths.count + output_paths.count) > MAX_INPUT_OUTPUT_PATHS
215
+ input_paths.clear
216
+ output_paths.clear
217
+ end
218
+ end
219
+
220
+ # Returns an extension in the target that corresponds to the
221
+ # resource's input extension.
222
+ #
223
+ # @param [String] input_extension
224
+ # The input extension to map to.
225
+ #
226
+ # @return [String] The output extension.
227
+ #
228
+ def output_extension_for_resource(input_extension)
229
+ case input_extension
230
+ when '.storyboard' then '.storyboardc'
231
+ when '.xib' then '.nib'
232
+ when '.framework' then '.framework'
233
+ when '.xcdatamodel' then '.mom'
234
+ when '.xcdatamodeld' then '.momd'
235
+ when '.xcmappingmodel' then '.cdm'
236
+ when '.xcassets' then '.car'
237
+ else input_extension
238
+ end
239
+ end
240
+
241
+ # Returns the resource output paths for all given input paths.
242
+ #
243
+ # @param [Array<String>] resource_input_paths
244
+ # The input paths to map to.
245
+ #
246
+ # @return [Array<String>] The resource output paths.
247
+ #
248
+ def resource_output_paths(resource_input_paths)
249
+ resource_input_paths.map do |resource_input_path|
250
+ base_path = '${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}'
251
+ extname = File.extname(resource_input_path)
252
+ basename = extname == '.xcassets' ? 'Assets' : File.basename(resource_input_path)
253
+ output_extension = TargetIntegrator.output_extension_for_resource(extname)
254
+ File.join(base_path, File.basename(basename, extname) + output_extension)
255
+ end.uniq
256
+ end
257
+ end
258
+
259
+ # Integrates the user project targets. Only the targets that do **not**
260
+ # already have the Pods library in their frameworks build phase are
261
+ # processed.
262
+ #
263
+ # @return [void]
264
+ #
265
+ def integrate!
266
+ UI.section(integration_message) do
267
+ XCConfigIntegrator.integrate(target, native_targets)
268
+
269
+ add_pods_library
270
+ add_embed_frameworks_script_phase
271
+ remove_embed_frameworks_script_phase_from_embedded_targets
272
+ add_copy_resources_script_phase
273
+ add_check_manifest_lock_script_phase
274
+ add_user_script_phases
275
+ end
276
+ end
277
+
278
+ # @return [String] a string representation suitable for debugging.
279
+ #
280
+ def inspect
281
+ "#<#{self.class} for target `#{target.label}'>"
282
+ end
283
+
284
+ private
285
+
286
+ # @!group Integration steps
287
+ #---------------------------------------------------------------------#
288
+
289
+ # Adds spec product reference to the frameworks build phase of the
290
+ # {TargetDefinition} integration libraries. Adds a file reference to
291
+ # the frameworks group of the project and adds it to the frameworks
292
+ # build phase of the targets.
293
+ #
294
+ # @return [void]
295
+ #
296
+ def add_pods_library
297
+ frameworks = user_project.frameworks_group
298
+ native_targets.each do |native_target|
299
+ build_phase = native_target.frameworks_build_phase
300
+
301
+ # Find and delete possible reference for the other product type
302
+ old_product_name = target.requires_frameworks? ? target.static_library_name : target.framework_name
303
+ old_product_ref = frameworks.files.find { |f| f.path == old_product_name }
304
+ if old_product_ref.present?
305
+ UI.message("Removing old Pod product reference #{old_product_name} from project.")
306
+ build_phase.remove_file_reference(old_product_ref)
307
+ frameworks.remove_reference(old_product_ref)
308
+ end
309
+
310
+ # Find or create and add a reference for the current product type
311
+ target_basename = target.product_basename
312
+ new_product_ref = frameworks.files.find { |f| f.path == target.product_name } ||
313
+ frameworks.new_product_ref_for_target(target_basename, target.product_type)
314
+ build_phase.build_file(new_product_ref) ||
315
+ build_phase.add_file_reference(new_product_ref, true)
316
+ end
317
+ end
318
+
319
+ # Find or create a 'Copy Pods Resources' build phase
320
+ #
321
+ # @return [void]
322
+ #
323
+ def add_copy_resources_script_phase
324
+ native_targets.each do |native_target|
325
+ puts "add copy phase native target : #{native_target}, target : #{target}"
326
+ script_path = target.copy_resources_script_relative_path
327
+ resource_paths_by_config = target.resource_paths_by_config
328
+ if resource_paths_by_config.values.all?(&:empty?)
329
+ TargetIntegrator.remove_copy_resources_script_phase_from_target(native_target)
330
+ else
331
+ resource_paths_flattened = resource_paths_by_config.values.flatten.uniq
332
+ input_paths = [target.copy_resources_script_relative_path, *resource_paths_flattened]
333
+ output_paths = TargetIntegrator.resource_output_paths(resource_paths_flattened)
334
+ TargetIntegrator.validate_input_output_path_limit(input_paths, output_paths)
335
+ TargetIntegrator.create_or_update_copy_resources_script_phase_to_target(native_target, script_path, input_paths, output_paths)
336
+ end
337
+ end
338
+ end
339
+
340
+ # Removes the embed frameworks build phase from embedded targets
341
+ #
342
+ # @note Older versions of CocoaPods would add this build phase to embedded
343
+ # targets. They should be removed on upgrade because embedded targets
344
+ # will have their frameworks embedded in their host targets.
345
+ #
346
+ def remove_embed_frameworks_script_phase_from_embedded_targets
347
+ return unless target.requires_host_target?
348
+ native_targets.each do |native_target|
349
+ if AggregateTarget::EMBED_FRAMEWORKS_IN_HOST_TARGET_TYPES.include? native_target.symbol_type
350
+ TargetIntegrator.remove_embed_frameworks_script_phase_from_target(native_target)
351
+ end
352
+ end
353
+ end
354
+
355
+ # Find or create a 'Embed Pods Frameworks' Copy Files Build Phase
356
+ #
357
+ # @return [void]
358
+ #
359
+ def add_embed_frameworks_script_phase
360
+ native_targets_to_embed_in.each do |native_target|
361
+ script_path = target.embed_frameworks_script_relative_path
362
+ framework_paths_by_config = target.framework_paths_by_config.values.flatten.uniq
363
+ if framework_paths_by_config.all?(&:empty?)
364
+ TargetIntegrator.remove_embed_frameworks_script_phase_from_target(native_target)
365
+ else
366
+ input_paths = [target.embed_frameworks_script_relative_path, *framework_paths_by_config.map { |fw| [fw[:input_path], fw[:dsym_input_path]] }.flatten.compact]
367
+ output_paths = framework_paths_by_config.map { |fw| [fw[:output_path], fw[:dsym_output_path]] }.flatten.compact.uniq
368
+ TargetIntegrator.validate_input_output_path_limit(input_paths, output_paths)
369
+ TargetIntegrator.create_or_update_embed_frameworks_script_phase_to_target(native_target, script_path, input_paths, output_paths)
370
+ end
371
+ end
372
+ end
373
+
374
+ # Updates all target script phases for the current target, including creating or updating, deleting
375
+ # and re-ordering.
376
+ #
377
+ # @return [void]
378
+ #
379
+ def add_user_script_phases
380
+ native_targets.each do |native_target|
381
+ TargetIntegrator.create_or_update_user_script_phases(target.target_definition.script_phases, native_target)
382
+ end
383
+ end
384
+
385
+ # Adds a shell script build phase responsible for checking if the Pods
386
+ # locked in the Pods/Manifest.lock file are in sync with the Pods defined
387
+ # in the Podfile.lock.
388
+ #
389
+ # @note The build phase is appended to the front because to fail
390
+ # fast.
391
+ #
392
+ # @return [void]
393
+ #
394
+ def add_check_manifest_lock_script_phase
395
+ phase_name = CHECK_MANIFEST_PHASE_NAME
396
+ native_targets.each do |native_target|
397
+ phase = TargetIntegrator.create_or_update_build_phase(native_target, BUILD_PHASE_PREFIX + phase_name)
398
+ native_target.build_phases.unshift(phase).uniq! unless native_target.build_phases.first == phase
399
+ phase.shell_script = <<-SH.strip_heredoc
400
+ diff "${PODS_PODFILE_DIR_PATH}/Podfile.lock" "${PODS_ROOT}/Manifest.lock" > /dev/null
401
+ if [ $? != 0 ] ; then
402
+ # print error to STDERR
403
+ echo "error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation." >&2
404
+ exit 1
405
+ fi
406
+ # This output is used by Xcode 'outputs' to avoid re-running this script phase.
407
+ echo "SUCCESS" > "${SCRIPT_OUTPUT_FILE_0}"
408
+ SH
409
+ phase.input_paths = %w(${PODS_PODFILE_DIR_PATH}/Podfile.lock ${PODS_ROOT}/Manifest.lock)
410
+ phase.output_paths = [target.check_manifest_lock_script_output_file_path]
411
+ end
412
+ end
413
+
414
+ private
415
+
416
+ # @!group Private Helpers
417
+ #---------------------------------------------------------------------#
418
+
419
+ # @return [Array<PBXNativeTarget>] The list of all the targets that
420
+ # match the given target.
421
+ #
422
+ def native_targets
423
+ @native_targets ||= target.user_targets
424
+ end
425
+
426
+ # @return [Array<PBXNativeTarget>] The list of all the targets that
427
+ # require that the pod frameworks are embedded in the output
428
+ # directory / product bundle.
429
+ #
430
+ def native_targets_to_embed_in
431
+ return [] if target.requires_host_target?
432
+ native_targets.select do |target|
433
+ EMBED_FRAMEWORK_TARGET_TYPES.include?(target.symbol_type)
434
+ end
435
+ end
436
+
437
+ # Read the project from the disk to ensure that it is up to date as
438
+ # other TargetIntegrators might have modified it.
439
+ #
440
+ # @return [Project]
441
+ #
442
+ def user_project
443
+ target.user_project
444
+ end
445
+
446
+ # @return [Specification::Consumer] the consumer for the specifications.
447
+ #
448
+ def spec_consumers
449
+ @spec_consumers ||= target.pod_targets.map(&:file_accessors).flatten.map(&:spec_consumer)
450
+ end
451
+
452
+ # @return [String] the message that should be displayed for the target
453
+ # integration.
454
+ #
455
+ def integration_message
456
+ "Integrating target `#{target.name}` " \
457
+ "(#{UI.path target.user_project_path} project)"
458
+ end
459
+ end
460
+ end
461
+ end
462
+ end