cocoapods 1.4.0 → 1.5.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +149 -0
  3. data/lib/cocoapods.rb +1 -0
  4. data/lib/cocoapods/command/outdated.rb +2 -2
  5. data/lib/cocoapods/command/repo/push.rb +5 -0
  6. data/lib/cocoapods/command/update.rb +21 -5
  7. data/lib/cocoapods/gem_version.rb +1 -1
  8. data/lib/cocoapods/generator/acknowledgements.rb +6 -3
  9. data/lib/cocoapods/generator/constant.rb +19 -0
  10. data/lib/cocoapods/generator/copy_resources_script.rb +15 -3
  11. data/lib/cocoapods/generator/embed_frameworks_script.rb +11 -2
  12. data/lib/cocoapods/generator/module_map.rb +56 -5
  13. data/lib/cocoapods/generator/xcconfig/aggregate_xcconfig.rb +19 -13
  14. data/lib/cocoapods/generator/xcconfig/pod_xcconfig.rb +4 -6
  15. data/lib/cocoapods/generator/xcconfig/xcconfig_helper.rb +25 -2
  16. data/lib/cocoapods/installer.rb +17 -8
  17. data/lib/cocoapods/installer/analyzer.rb +48 -38
  18. data/lib/cocoapods/installer/analyzer/analysis_result.rb +11 -0
  19. data/lib/cocoapods/installer/analyzer/locking_dependency_analyzer.rb +7 -6
  20. data/lib/cocoapods/installer/analyzer/pod_variant.rb +8 -8
  21. data/lib/cocoapods/installer/analyzer/pod_variant_set.rb +3 -3
  22. data/lib/cocoapods/installer/analyzer/podfile_dependency_cache.rb +54 -0
  23. data/lib/cocoapods/installer/analyzer/specs_state.rb +16 -16
  24. data/lib/cocoapods/installer/analyzer/target_inspector.rb +7 -11
  25. data/lib/cocoapods/installer/pod_source_installer.rb +2 -3
  26. data/lib/cocoapods/installer/podfile_validator.rb +11 -10
  27. data/lib/cocoapods/installer/user_project_integrator/target_integrator.rb +72 -28
  28. data/lib/cocoapods/installer/xcode/pods_project_generator.rb +8 -2
  29. data/lib/cocoapods/installer/xcode/pods_project_generator/aggregate_target_installer.rb +9 -0
  30. data/lib/cocoapods/installer/xcode/pods_project_generator/file_references_installer.rb +32 -24
  31. data/lib/cocoapods/installer/xcode/pods_project_generator/pod_target_installer.rb +97 -54
  32. data/lib/cocoapods/installer/xcode/pods_project_generator/pod_target_integrator.rb +9 -11
  33. data/lib/cocoapods/installer/xcode/pods_project_generator/target_installer.rb +4 -9
  34. data/lib/cocoapods/installer/xcode/target_validator.rb +32 -18
  35. data/lib/cocoapods/project.rb +32 -17
  36. data/lib/cocoapods/resolver.rb +59 -31
  37. data/lib/cocoapods/resolver/lazy_specification.rb +28 -18
  38. data/lib/cocoapods/sandbox.rb +2 -4
  39. data/lib/cocoapods/sandbox/file_accessor.rb +25 -9
  40. data/lib/cocoapods/sandbox/headers_store.rb +31 -6
  41. data/lib/cocoapods/sandbox/path_list.rb +36 -46
  42. data/lib/cocoapods/target.rb +7 -4
  43. data/lib/cocoapods/target/aggregate_target.rb +10 -8
  44. data/lib/cocoapods/target/pod_target.rb +87 -20
  45. data/lib/cocoapods/user_interface/error_report.rb +1 -1
  46. data/lib/cocoapods/user_interface/inspector_reporter.rb +4 -4
  47. data/lib/cocoapods/validator.rb +40 -29
  48. metadata +11 -9
@@ -57,7 +57,7 @@ module Pod
57
57
  def initialize(root)
58
58
  FileUtils.mkdir_p(root)
59
59
  @root = Pathname.new(root).realpath
60
- @public_headers = HeadersStore.new(self, 'Public')
60
+ @public_headers = HeadersStore.new(self, 'Public', :public)
61
61
  @predownloaded_pods = []
62
62
  @checkout_sources = {}
63
63
  @development_pods = {}
@@ -389,9 +389,7 @@ module Pod
389
389
  #
390
390
  def local_podspec(name)
391
391
  root_name = Specification.root_name(name)
392
- if path = development_pods[root_name]
393
- Pathname.new(path)
394
- end
392
+ development_pods[root_name]
395
393
  end
396
394
 
397
395
  #-------------------------------------------------------------------------#
@@ -196,9 +196,9 @@ module Pod
196
196
  # shipped with the Pod.
197
197
  #
198
198
  def vendored_frameworks_headers
199
- vendored_frameworks.map do |framework|
199
+ vendored_frameworks.flat_map do |framework|
200
200
  self.class.vendored_frameworks_headers(framework)
201
- end.flatten.uniq
201
+ end.uniq
202
202
  end
203
203
 
204
204
  # @return [Array<Pathname>] The paths of the library bundles that come
@@ -278,11 +278,7 @@ module Pod
278
278
  # specification or auto-detected.
279
279
  #
280
280
  def license
281
- if file = spec_consumer.license[:file]
282
- path_list.root + file
283
- else
284
- path_list.glob([GLOB_PATTERNS[:license]]).first
285
- end
281
+ spec_license || path_list.glob([GLOB_PATTERNS[:license]]).first
286
282
  end
287
283
 
288
284
  # @return [Pathname, Nil] The path of the custom module map file of the
@@ -305,17 +301,37 @@ module Pod
305
301
  path_list.glob([GLOB_PATTERNS[:docs]])
306
302
  end
307
303
 
304
+ # @return [Pathname] The path of the license file specified in the
305
+ # specification, if it exists
306
+ #
307
+ def spec_license
308
+ if file = spec_consumer.license[:file]
309
+ absolute_path = root + file
310
+ absolute_path if File.exist?(absolute_path)
311
+ end
312
+ end
313
+
308
314
  # @return [Array<Pathname>] Paths to include for local pods to assist in development
309
315
  #
310
316
  def developer_files
311
317
  podspecs = specs
312
318
  result = [module_map, prefix_header]
319
+
320
+ if license_path = spec_consumer.license[:file]
321
+ license_path = root + license_path
322
+ unless File.exist?(license_path)
323
+ UI.warn "A license was specified in podspec `#{spec.name}` but the file does not exist - #{license_path}"
324
+ end
325
+ end
326
+
313
327
  if podspecs.size <= 1
314
328
  result += [license, readme, podspecs, docs]
315
329
  else
330
+ # Manually add non-globbing files since there are multiple podspecs in the same folder
316
331
  result << podspec_file
317
- if file = spec_consumer.license[:file]
318
- result << root + file
332
+ if license_file = spec_license
333
+ absolute_path = root + license_file
334
+ result << absolute_path if File.exist?(absolute_path)
319
335
  end
320
336
  end
321
337
  result.compact.flatten.sort
@@ -4,6 +4,8 @@ module Pod
4
4
  # the header search paths.
5
5
  #
6
6
  class HeadersStore
7
+ SEARCH_PATHS_KEY = Struct.new(:platform_name, :target_name, :use_modular_headers)
8
+
7
9
  # @return [Pathname] the absolute path of this header directory.
8
10
  #
9
11
  def root
@@ -20,25 +22,48 @@ module Pod
20
22
  # the relative path to the sandbox root and hence to the Pods
21
23
  # project.
22
24
  #
23
- def initialize(sandbox, relative_path)
25
+ # @param [Symbol] visibility_scope
26
+ # the header visibility scope to use in this store. Can be `:private` or `:public`.
27
+ #
28
+ def initialize(sandbox, relative_path, visibility_scope)
24
29
  @sandbox = sandbox
25
30
  @relative_path = relative_path
26
31
  @search_paths = []
32
+ @search_paths_cache = {}
33
+ @visibility_scope = visibility_scope
27
34
  end
28
35
 
29
36
  # @param [Platform] platform
30
37
  # the platform for which the header search paths should be
31
- # returned
38
+ # returned.
39
+ #
40
+ # @param [String] target_name
41
+ # the target for which the header search paths should be
42
+ # returned. Can be `nil` in which case all headers that match the platform
43
+ # will be returned.
44
+ #
45
+ # @param [Boolean] use_modular_headers
46
+ # whether the search paths generated should use modular (stricter) style.
32
47
  #
33
48
  # @return [Array<String>] All the search paths of the header directory in
34
49
  # xcconfig format. The paths are specified relative to the pods
35
50
  # root with the `${PODS_ROOT}` variable.
36
51
  #
37
- def search_paths(platform)
38
- platform_search_paths = @search_paths.select { |entry| entry[:platform] == platform.name }
39
-
52
+ def search_paths(platform, target_name = nil, use_modular_headers = false)
53
+ key = SEARCH_PATHS_KEY.new(platform.name, target_name, use_modular_headers)
54
+ return @search_paths_cache[key] if @search_paths_cache.key?(key)
55
+ search_paths = @search_paths.select do |entry|
56
+ matches_platform = entry[:platform] == platform.name
57
+ matches_target = target_name.nil? || (entry[:path].basename.to_s == target_name)
58
+ matches_platform && matches_target
59
+ end
40
60
  headers_dir = root.relative_path_from(sandbox.root).dirname
41
- ["${PODS_ROOT}/#{headers_dir}/#{@relative_path}"] + platform_search_paths.uniq.map { |entry| "${PODS_ROOT}/#{headers_dir}/#{entry[:path]}" }
61
+ @search_paths_cache[key] = search_paths.flat_map do |entry|
62
+ paths = []
63
+ paths << "${PODS_ROOT}/#{headers_dir}/#{@relative_path}" if !use_modular_headers || @visibility_scope == :public
64
+ paths << "${PODS_ROOT}/#{headers_dir}/#{entry[:path]}" if !use_modular_headers || @visibility_scope == :private
65
+ paths
66
+ end.uniq
42
67
  end
43
68
 
44
69
  # Removes the directory as it is regenerated from scratch during each
@@ -1,4 +1,5 @@
1
1
  require 'active_support/multibyte/unicode'
2
+ require 'find'
2
3
 
3
4
  module Pod
4
5
  class Sandbox
@@ -50,23 +51,23 @@ module Pod
50
51
  unless root.exist?
51
52
  raise Informative, "Attempt to read non existent folder `#{root}`."
52
53
  end
53
- escaped_root = escape_path_for_glob(root)
54
-
55
- absolute_paths = Pathname.glob(escaped_root + '**/*', File::FNM_DOTMATCH).lazy
56
- dirs_and_files = absolute_paths.reject { |path| path.basename.to_s =~ /^\.\.?$/ }
57
- dirs, files = dirs_and_files.partition { |path| File.directory?(path) }
58
54
 
55
+ dirs = []
56
+ files = []
59
57
  root_length = root.cleanpath.to_s.length + File::SEPARATOR.length
60
- sorted_relative_paths_from_full_paths = lambda do |paths|
61
- relative_paths = paths.lazy.map do |path|
62
- path_string = path.to_s
63
- path_string.slice(root_length, path_string.length - root_length)
64
- end
65
- relative_paths.sort_by(&:upcase)
58
+ Find.find(root.to_s) do |f|
59
+ directory = File.directory?(f)
60
+ f = f.slice(root_length, f.length - root_length)
61
+ next if f.nil?
62
+
63
+ (directory ? dirs : files) << f
66
64
  end
67
65
 
68
- @dirs = sorted_relative_paths_from_full_paths.call(dirs)
69
- @files = sorted_relative_paths_from_full_paths.call(files)
66
+ dirs.sort_by!(&:upcase)
67
+ files.sort_by!(&:upcase)
68
+
69
+ @dirs = dirs
70
+ @files = files
70
71
  @glob_cache = {}
71
72
  end
72
73
 
@@ -87,7 +88,7 @@ module Pod
87
88
  # @return [Array<Pathname>]
88
89
  #
89
90
  def glob(patterns, options = {})
90
- relative_glob(patterns, options).map { |p| root + p }
91
+ relative_glob(patterns, options).map { |p| root.join(p) }
91
92
  end
92
93
 
93
94
  # The list of relative paths that are case insensitively matched by a
@@ -127,19 +128,27 @@ module Pod
127
128
  else
128
129
  full_list = files
129
130
  end
130
-
131
- list = Array(patterns).map do |pattern|
132
- if directory?(pattern) && dir_pattern
133
- pattern += '/' unless pattern.end_with?('/')
134
- pattern += dir_pattern
135
- end
136
- expanded_patterns = dir_glob_equivalent_patterns(pattern)
137
- full_list.select do |path|
138
- expanded_patterns.any? do |p|
139
- File.fnmatch(p, path, File::FNM_CASEFOLD | File::FNM_PATHNAME)
131
+ patterns_array = Array(patterns)
132
+ exact_matches = (full_list & patterns_array).to_set
133
+
134
+ unless patterns_array.empty?
135
+ list = patterns_array.flat_map do |pattern|
136
+ if exact_matches.include?(pattern)
137
+ pattern
138
+ else
139
+ if directory?(pattern) && dir_pattern
140
+ pattern += '/' unless pattern.end_with?('/')
141
+ pattern += dir_pattern
142
+ end
143
+ expanded_patterns = dir_glob_equivalent_patterns(pattern)
144
+ full_list.select do |path|
145
+ expanded_patterns.any? do |p|
146
+ File.fnmatch(p, path, File::FNM_CASEFOLD | File::FNM_PATHNAME)
147
+ end
148
+ end
140
149
  end
141
150
  end
142
- end.flatten
151
+ end
143
152
 
144
153
  list = list.map { |path| Pathname.new(path) }
145
154
  if exclude_patterns
@@ -198,35 +207,16 @@ module Pod
198
207
  else
199
208
  patterns = [pattern]
200
209
  values_by_set.each do |set, values|
201
- patterns = patterns.map do |old_pattern|
210
+ patterns = patterns.flat_map do |old_pattern|
202
211
  values.map do |value|
203
212
  old_pattern.gsub(set, value)
204
213
  end
205
- end.flatten
214
+ end
206
215
  end
207
216
  patterns
208
217
  end
209
218
  end
210
219
 
211
- # Escapes the glob metacharacters from a given path so it can used in
212
- # Dir#glob and similar methods.
213
- #
214
- # @note See CocoaPods/CocoaPods#862.
215
- #
216
- # @param [String, Pathname] path
217
- # The path to escape.
218
- #
219
- # @return [Pathname] The escaped path.
220
- #
221
- def escape_path_for_glob(path)
222
- result = path.to_s
223
- characters_to_escape = ['[', ']', '{', '}', '?', '*']
224
- characters_to_escape.each do |character|
225
- result.gsub!(character, "\\#{character}")
226
- end
227
- Pathname.new(result)
228
- end
229
-
230
220
  #-----------------------------------------------------------------------#
231
221
  end
232
222
  end
@@ -74,7 +74,7 @@ module Pod
74
74
  # #requires_frameworks?.
75
75
  #
76
76
  def product_type
77
- requires_frameworks? && !static_framework? ? :framework : :static_library
77
+ requires_frameworks? ? :framework : :static_library
78
78
  end
79
79
 
80
80
  # @return [String] A string suitable for debugging.
@@ -95,7 +95,9 @@ module Pod
95
95
  # @return [Boolean] Whether the target should build a static framework.
96
96
  #
97
97
  def static_framework?
98
- !is_a?(Pod::AggregateTarget) && specs.any? && specs.flat_map(&:root).all?(&:static_framework)
98
+ return if is_a?(Pod::AggregateTarget)
99
+ return if specs.empty?
100
+ specs.all? { |spec| spec.root.static_framework }
99
101
  end
100
102
 
101
103
  #-------------------------------------------------------------------------#
@@ -148,14 +150,15 @@ module Pod
148
150
  # module map.
149
151
  #
150
152
  def umbrella_header_path
151
- support_files_dir + "#{label}-umbrella.h"
153
+ module_map_path.parent + "#{label}-umbrella.h"
152
154
  end
153
155
 
154
156
  # @return [Pathname] the absolute path of the LLVM module map file that
155
157
  # defines the module structure for the compiler.
156
158
  #
157
159
  def module_map_path
158
- support_files_dir + "#{label}.modulemap"
160
+ basename = "#{label}.modulemap"
161
+ support_files_dir + basename
159
162
  end
160
163
 
161
164
  # @return [Pathname] the absolute path of the bridge support file.
@@ -140,7 +140,7 @@ module Pod
140
140
  # @return [Array<AggregateTarget>] The aggregate targets whose pods this
141
141
  # target must be able to import, but will not directly link against.
142
142
  #
143
- attr_accessor :search_paths_aggregate_targets
143
+ attr_reader :search_paths_aggregate_targets
144
144
 
145
145
  # @param [String] build_configuration The build configuration for which the
146
146
  # the pod targets should be returned.
@@ -149,15 +149,20 @@ module Pod
149
149
  # configuration.
150
150
  #
151
151
  def pod_targets_for_build_configuration(build_configuration)
152
- pod_targets.select do |pod_target|
152
+ @pod_targets_for_build_configuration ||= {}
153
+ @pod_targets_for_build_configuration[build_configuration] ||= pod_targets.select do |pod_target|
153
154
  pod_target.include_in_build_config?(target_definition, build_configuration)
154
155
  end
155
156
  end
156
157
 
158
+ def pod_targets_to_link
159
+ @pod_targets_to_link ||= pod_targets.to_set - search_paths_aggregate_targets.flat_map(&:pod_targets)
160
+ end
161
+
157
162
  # @return [Array<Specification>] The specifications used by this aggregate target.
158
163
  #
159
164
  def specs
160
- pod_targets.map(&:specs).flatten
165
+ pod_targets.flat_map(&:specs)
161
166
  end
162
167
 
163
168
  # @return [Hash{Symbol => Array<Specification>}] The pod targets for each
@@ -191,9 +196,7 @@ module Pod
191
196
  @framework_paths_by_config ||= begin
192
197
  framework_paths_by_config = {}
193
198
  user_build_configurations.keys.each do |config|
194
- relevant_pod_targets = pod_targets.select do |pod_target|
195
- pod_target.include_in_build_config?(target_definition, config)
196
- end
199
+ relevant_pod_targets = pod_targets_for_build_configuration(config)
197
200
  framework_paths_by_config[config] = relevant_pod_targets.flat_map { |pt| pt.framework_paths(false) }
198
201
  end
199
202
  framework_paths_by_config
@@ -208,8 +211,7 @@ module Pod
208
211
  pod_target.should_build? && pod_target.requires_frameworks? && !pod_target.static_framework?
209
212
  end
210
213
  user_build_configurations.keys.each_with_object({}) do |config, resources_by_config|
211
- resources_by_config[config] = relevant_pod_targets.flat_map do |pod_target|
212
- next [] unless pod_target.include_in_build_config?(target_definition, config)
214
+ resources_by_config[config] = (relevant_pod_targets & pod_targets_for_build_configuration(config)).flat_map do |pod_target|
213
215
  (pod_target.resource_paths(false) + [bridge_support_file].compact).uniq
214
216
  end
215
217
  end
@@ -49,11 +49,12 @@ module Pod
49
49
  raise "Can't initialize a PodTarget with only abstract TargetDefinitions" if target_definitions.all?(&:abstract?)
50
50
  raise "Can't initialize a PodTarget with an empty string scope suffix!" if scope_suffix == ''
51
51
  super()
52
- @specs = specs
52
+ @specs = specs.dup.freeze
53
+ @test_specs, @non_test_specs = @specs.partition(&:test_specification?)
53
54
  @target_definitions = target_definitions
54
55
  @sandbox = sandbox
55
56
  @scope_suffix = scope_suffix
56
- @build_headers = Sandbox::HeadersStore.new(sandbox, 'Private')
57
+ @build_headers = Sandbox::HeadersStore.new(sandbox, 'Private', :private)
57
58
  @file_accessors = []
58
59
  @resource_bundle_targets = []
59
60
  @test_resource_bundle_targets = []
@@ -162,17 +163,18 @@ module Pod
162
163
  # to this target.
163
164
  attr_reader :test_resource_bundle_targets
164
165
 
165
- # @return [Bool] Whether or not this target should be build.
166
+ # @return [Bool] Whether or not this target should be built.
166
167
  #
167
- # A target should not be build if it has no source files.
168
+ # A target should not be built if it has no source files.
168
169
  #
169
170
  def should_build?
170
171
  return @should_build if defined? @should_build
171
- @should_build = begin
172
- source_files = file_accessors.flat_map(&:source_files)
173
- source_files -= file_accessors.flat_map(&:headers)
174
- !source_files.empty? || contains_script_phases?
175
- end
172
+
173
+ return @should_build = true if contains_script_phases?
174
+
175
+ source_files = file_accessors.flat_map(&:source_files)
176
+ source_files -= file_accessors.flat_map(&:headers)
177
+ @should_build = !source_files.empty?
176
178
  end
177
179
 
178
180
  # @return [Array<Specification::Consumer>] the specification consumers for
@@ -193,10 +195,24 @@ module Pod
193
195
  end
194
196
  end
195
197
 
198
+ # @return [Boolean] Whether the target defines a "module"
199
+ # (and thus will need a module map and umbrella header).
200
+ #
201
+ # @note Static library targets can temporarily opt in to this behavior by setting
202
+ # `DEFINES_MODULE = YES` in their specification's `pod_target_xcconfig`.
203
+ #
204
+ def defines_module?
205
+ return @defines_module if defined?(@defines_module)
206
+ return @defines_module = true if uses_swift? || requires_frameworks?
207
+ return @defines_module = true if target_definitions.any? { |td| td.build_pod_as_module?(pod_name) }
208
+
209
+ @defines_module = non_test_specs.any? { |s| s.consumer(platform).pod_target_xcconfig['DEFINES_MODULE'] == 'YES' }
210
+ end
211
+
196
212
  # @return [Array<Hash{Symbol=>String}>] An array of hashes where each hash represents a single script phase.
197
213
  #
198
214
  def script_phases
199
- spec_consumers.map(&:script_phases).flatten
215
+ spec_consumers.flat_map(&:script_phases)
200
216
  end
201
217
 
202
218
  # @return [Boolean] Whether the target contains any script phases.
@@ -215,20 +231,16 @@ module Pod
215
231
  # @return [Boolean] Whether the target has any tests specifications.
216
232
  #
217
233
  def contains_test_specifications?
218
- specs.any?(&:test_specification?)
234
+ !test_specs.empty?
219
235
  end
220
236
 
221
237
  # @return [Array<Specification>] All of the test specs within this target.
222
238
  #
223
- def test_specs
224
- specs.select(&:test_specification?)
225
- end
239
+ attr_reader :test_specs
226
240
 
227
- # @return [Array<Specification>] All of the non test specs within this target.
241
+ # @return [Array<Specification>] All of the specs within this target that are not test specs.
228
242
  #
229
- def non_test_specs
230
- specs.reject(&:test_specification?)
231
- end
243
+ attr_reader :non_test_specs
232
244
 
233
245
  # @return [Array<Symbol>] All of the test supported types within this target.
234
246
  #
@@ -365,6 +377,20 @@ module Pod
365
377
  root_spec.name
366
378
  end
367
379
 
380
+ # @return [Pathname] the absolute path of the LLVM module map file that
381
+ # defines the module structure for the compiler.
382
+ #
383
+ def module_map_path
384
+ basename = "#{label}.modulemap"
385
+ if requires_frameworks?
386
+ super
387
+ elsif file_accessors.any?(&:module_map)
388
+ build_headers.root + product_module_name + basename
389
+ else
390
+ sandbox.public_headers.root + product_module_name + basename
391
+ end
392
+ end
393
+
368
394
  # @param [String] bundle_name
369
395
  # The name of the bundle product, which is given by the +spec+.
370
396
  #
@@ -410,6 +436,15 @@ module Pod
410
436
  support_files_dir + "#{test_target_label(test_type)}-frameworks.sh"
411
437
  end
412
438
 
439
+ # @param [Symbol] test_type
440
+ # The test type this Info.plist path is for.
441
+ #
442
+ # @return [Pathname] The absolute path of the Info.plist for the given test type.
443
+ #
444
+ def info_plist_path_for_test_type(test_type)
445
+ support_files_dir + "#{test_target_label(test_type)}-Info.plist"
446
+ end
447
+
413
448
  # @return [Pathname] the absolute path of the prefix header file.
414
449
  #
415
450
  def prefix_header_path
@@ -496,10 +531,8 @@ module Pod
496
531
 
497
532
  if whitelists.empty?
498
533
  @build_config_cache[key] = true
499
- true
500
534
  elsif whitelists.count == 1
501
535
  @build_config_cache[key] = whitelists.first
502
- whitelists.first
503
536
  else
504
537
  raise Informative, "The subspecs of `#{pod_name}` are linked to " \
505
538
  "different build configurations for the `#{target_definition}` " \
@@ -567,6 +600,40 @@ module Pod
567
600
  [version.major, version.minor, version.patch].join('.')
568
601
  end
569
602
 
603
+ # @param [Boolean] include_test_dependent_targets
604
+ # whether to include header search paths for test dependent targets
605
+ #
606
+ # @return [Array<String>] The set of header search paths this target uses.
607
+ #
608
+ def header_search_paths(include_test_dependent_targets = false)
609
+ header_search_paths = []
610
+ header_search_paths.concat(build_headers.search_paths(platform, nil, uses_modular_headers?))
611
+ header_search_paths.concat(sandbox.public_headers.search_paths(platform, pod_name, uses_modular_headers?))
612
+ dependent_targets = recursive_dependent_targets
613
+ dependent_targets += recursive_test_dependent_targets if include_test_dependent_targets
614
+ dependent_targets.each do |dependent_target|
615
+ header_search_paths.concat(sandbox.public_headers.search_paths(platform, dependent_target.pod_name, defines_module? && dependent_target.uses_modular_headers?(false)))
616
+ end
617
+ header_search_paths.uniq
618
+ end
619
+
620
+ protected
621
+
622
+ # Returns whether the pod target should use modular headers.
623
+ #
624
+ # @param [Boolean] only_if_defines_modules
625
+ # whether the use of modular headers should require the target to define a module
626
+ #
627
+ # @note This must return false when a pod has a `header_mappings_dir`,
628
+ # as that allows the spec to completely customize the header structure, and
629
+ # therefore it might not be expecting the module name to be prepended
630
+ # to imports at all.
631
+ #
632
+ def uses_modular_headers?(only_if_defines_modules = true)
633
+ return false if only_if_defines_modules && !defines_module?
634
+ spec_consumers.none?(&:header_mappings_dir)
635
+ end
636
+
570
637
  private
571
638
 
572
639
  # @param [TargetDefinition] target_definition