cocoapods-dykit 0.5.2 → 0.5.3

Sign up to get free protection for your applications and to get access to all the features.
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