cocoapods 1.4.0 → 1.5.0.beta.1

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 (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