cocoapods 1.10.1 → 1.11.0.rc.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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +224 -5
  3. data/README.md +11 -11
  4. data/lib/cocoapods/command/outdated.rb +12 -1
  5. data/lib/cocoapods/command/repo/push.rb +17 -0
  6. data/lib/cocoapods/command/spec/cat.rb +3 -1
  7. data/lib/cocoapods/command/spec/lint.rb +1 -1
  8. data/lib/cocoapods/command/spec/which.rb +3 -1
  9. data/lib/cocoapods/command/spec.rb +18 -9
  10. data/lib/cocoapods/config.rb +1 -1
  11. data/lib/cocoapods/downloader/cache.rb +95 -6
  12. data/lib/cocoapods/downloader.rb +4 -2
  13. data/lib/cocoapods/external_sources/podspec_source.rb +1 -1
  14. data/lib/cocoapods/gem_version.rb +1 -1
  15. data/lib/cocoapods/generator/acknowledgements.rb +1 -1
  16. data/lib/cocoapods/generator/app_target_helper.rb +7 -3
  17. data/lib/cocoapods/generator/copy_dsyms_script.rb +4 -4
  18. data/lib/cocoapods/generator/copy_xcframework_script.rb +4 -48
  19. data/lib/cocoapods/generator/embed_frameworks_script.rb +2 -1
  20. data/lib/cocoapods/generator/script_phase_constants.rb +1 -0
  21. data/lib/cocoapods/installer/analyzer/sandbox_analyzer.rb +31 -4
  22. data/lib/cocoapods/installer/analyzer.rb +12 -8
  23. data/lib/cocoapods/installer/podfile_validator.rb +2 -2
  24. data/lib/cocoapods/installer/pre_integrate_hooks_context.rb +9 -0
  25. data/lib/cocoapods/installer/project_cache/project_cache_analyzer.rb +9 -2
  26. data/lib/cocoapods/installer/project_cache/project_installation_cache.rb +15 -2
  27. data/lib/cocoapods/installer/project_cache/target_cache_key.rb +7 -4
  28. data/lib/cocoapods/installer/user_project_integrator/target_integrator.rb +149 -9
  29. data/lib/cocoapods/installer/xcode/pods_project_generator/app_host_installer.rb +10 -3
  30. data/lib/cocoapods/installer/xcode/pods_project_generator/file_references_installer.rb +25 -6
  31. data/lib/cocoapods/installer/xcode/pods_project_generator/pod_target_dependency_installer.rb +6 -19
  32. data/lib/cocoapods/installer/xcode/pods_project_generator/pod_target_installer.rb +70 -58
  33. data/lib/cocoapods/installer/xcode/pods_project_generator/pod_target_integrator.rb +48 -6
  34. data/lib/cocoapods/installer/xcode/pods_project_generator/target_installation_result.rb +2 -2
  35. data/lib/cocoapods/installer/xcode/pods_project_generator/target_installer.rb +2 -5
  36. data/lib/cocoapods/installer/xcode/pods_project_generator.rb +1 -1
  37. data/lib/cocoapods/installer.rb +52 -4
  38. data/lib/cocoapods/resolver.rb +4 -4
  39. data/lib/cocoapods/sandbox/file_accessor.rb +57 -10
  40. data/lib/cocoapods/sandbox/headers_store.rb +3 -1
  41. data/lib/cocoapods/sandbox/path_list.rb +1 -1
  42. data/lib/cocoapods/sandbox/pod_dir_cleaner.rb +1 -1
  43. data/lib/cocoapods/sources_manager.rb +14 -8
  44. data/lib/cocoapods/target/aggregate_target.rb +23 -1
  45. data/lib/cocoapods/target/build_settings.rb +45 -36
  46. data/lib/cocoapods/target/pod_target.rb +47 -22
  47. data/lib/cocoapods/user_interface.rb +4 -0
  48. data/lib/cocoapods/validator.rb +25 -5
  49. data/lib/cocoapods/version_metadata.rb +1 -1
  50. data/lib/cocoapods/xcode/xcframework.rb +8 -3
  51. metadata +28 -21
@@ -79,12 +79,12 @@ module Pod
79
79
  # @param [Specification] spec
80
80
  # The specification to base from in order to find the native target.
81
81
  #
82
- # @return [PBXNativeTarget] the native target to use or `nil` if none is found.
82
+ # @return [PBXNativeTarget, Nil] the native target to use or `nil` if none is found.
83
83
  #
84
84
  def native_target_for_spec(spec)
85
85
  return native_target if spec.library_specification?
86
86
  return test_native_target_from_spec(spec) if spec.test_specification?
87
- return app_native_target_from_spec(spec) if spec.app_specification?
87
+ app_native_target_from_spec(spec) if spec.app_specification?
88
88
  end
89
89
 
90
90
  # @return [Hash{PBXNativeTarget => Specification}] a hash where the keys are the test native targets and the value
@@ -56,11 +56,8 @@ module Pod
56
56
  name = target.label
57
57
  platform = target.platform.name
58
58
  language = target.uses_swift? ? :swift : :objc
59
- native_target = project.new_target(product_type, name, platform, deployment_target, nil, language)
60
-
61
- product_name = target.product_name
62
- product = native_target.product_reference
63
- product.name = product_name
59
+ native_target = project.new_target(product_type, name, platform, deployment_target, nil, language, target.product_basename)
60
+ native_target.product_reference.name = name
64
61
 
65
62
  target.user_build_configurations.each do |bc_name, type|
66
63
  native_target.add_build_configuration(bc_name, type)
@@ -247,7 +247,7 @@ module Pod
247
247
  end
248
248
  is_custom_host = !hosted_test_specs_by_host.empty?
249
249
  specs.each do |spec|
250
- scheme_name = spec.spec_type == :library ? pod_target.label : pod_target.non_library_spec_label(spec)
250
+ scheme_name = pod_target.spec_label(spec)
251
251
  scheme_configuration = pod_target.scheme_for_spec(spec)
252
252
  if !scheme_configuration.empty? || is_custom_host
253
253
  scheme_path = Xcodeproj::XCScheme.user_data_dir(project.path) + "#{scheme_name}.xcscheme"
@@ -36,6 +36,7 @@ module Pod
36
36
  autoload :PreInstallHooksContext, 'cocoapods/installer/pre_install_hooks_context'
37
37
  autoload :BaseInstallHooksContext, 'cocoapods/installer/base_install_hooks_context'
38
38
  autoload :PostIntegrateHooksContext, 'cocoapods/installer/post_integrate_hooks_context'
39
+ autoload :PreIntegrateHooksContext, 'cocoapods/installer/pre_integrate_hooks_context'
39
40
  autoload :SourceProviderHooksContext, 'cocoapods/installer/source_provider_hooks_context'
40
41
  autoload :PodfileValidator, 'cocoapods/installer/podfile_validator'
41
42
  autoload :PodSourceInstaller, 'cocoapods/installer/pod_source_installer'
@@ -175,6 +176,7 @@ module Pod
175
176
  end
176
177
 
177
178
  def integrate
179
+ run_podfile_pre_integrate_hooks
178
180
  generate_pods_project
179
181
  if installation_options.integrate_targets?
180
182
  integrate_user_project
@@ -199,7 +201,7 @@ module Pod
199
201
 
200
202
  force_clean_install = clean_install || project_cache_version.version != Version.create(VersionMetadata.project_cache_version)
201
203
  cache_result = ProjectCache::ProjectCacheAnalyzer.new(sandbox, installation_cache, analysis_result.all_user_build_configurations,
202
- object_version, plugins, pod_targets, aggregate_targets, :clean_install => force_clean_install).analyze
204
+ object_version, plugins, pod_targets, aggregate_targets, installation_options.to_h, :clean_install => force_clean_install).analyze
203
205
  aggregate_targets_to_generate = cache_result.aggregate_targets_to_generate || []
204
206
  pod_targets_to_generate = cache_result.pod_targets_to_generate
205
207
  (aggregate_targets_to_generate + pod_targets_to_generate).each do |target|
@@ -627,6 +629,15 @@ module Pod
627
629
  title_options)
628
630
  end
629
631
 
632
+ # Runs the registered callbacks for the plugins pre integrate hooks.
633
+ #
634
+ def run_plugins_pre_integrate_hooks
635
+ if any_plugin_pre_integrate_hooks?
636
+ context = PreIntegrateHooksContext.generate(sandbox, pods_project, aggregate_targets)
637
+ HooksManager.run(:pre_integrate, context, plugins)
638
+ end
639
+ end
640
+
630
641
  # Runs the registered callbacks for the plugins post install hooks.
631
642
  #
632
643
  def run_plugins_post_install_hooks
@@ -650,6 +661,12 @@ module Pod
650
661
  end
651
662
  end
652
663
 
664
+ # @return [Boolean] whether there are any plugin pre-integrate hooks to run
665
+ #
666
+ def any_plugin_pre_integrate_hooks?
667
+ HooksManager.hooks_to_run(:pre_integrate, plugins).any?
668
+ end
669
+
653
670
  # @return [Boolean] whether there are any plugin post-install hooks to run
654
671
  #
655
672
  def any_plugin_post_install_hooks?
@@ -747,7 +764,7 @@ module Pod
747
764
  def warn_for_installed_script_phases
748
765
  pods_to_install = sandbox_state.added | sandbox_state.changed
749
766
  pod_targets.group_by(&:pod_name).each do |name, pod_targets|
750
- if pods_to_install.include?(name)
767
+ if pods_to_install.include?(name) && !sandbox.local?(name)
751
768
  script_phase_count = pod_targets.inject(0) { |sum, target| sum + target.script_phases.count }
752
769
  unless script_phase_count.zero?
753
770
  UI.warn "#{name} has added #{script_phase_count} #{'script phase'.pluralize(script_phase_count)}. " \
@@ -766,9 +783,11 @@ module Pod
766
783
  #
767
784
  def warn_for_removing_git_master_specs_repo
768
785
  return unless installation_options.warn_for_unused_master_specs_repo?
769
- podfile_master_source = podfile.sources.find { |source| source == MASTER_SPECS_REPO_GIT_URL }
786
+ plugin_sources = run_source_provider_hooks
787
+ all_sources = podfile.sources + plugin_sources.map(&:url)
788
+ master_source = all_sources.find { |source| source == MASTER_SPECS_REPO_GIT_URL }
770
789
  master_repo = config.sources_manager.all.find { |s| s.url == MASTER_SPECS_REPO_GIT_URL }
771
- if podfile_master_source.nil? && !master_repo.nil?
790
+ if master_source.nil? && !master_repo.nil?
772
791
  UI.warn 'Your project does not explicitly specify the CocoaPods master specs repo. Since CDN is now used as the' \
773
792
  ' default, you may safely remove it from your repos directory via `pod repo remove master`. To suppress this warning' \
774
793
  ' please add `warn_for_unused_master_specs_repo => false` to your Podfile.'
@@ -815,6 +834,7 @@ module Pod
815
834
  installation_cache.update_project_object_version!(cache_analysis_result.project_object_version)
816
835
  installation_cache.update_build_configurations!(cache_analysis_result.build_configurations)
817
836
  installation_cache.update_podfile_plugins!(plugins)
837
+ installation_cache.update_installation_options!(installation_options.to_h)
818
838
  installation_cache.save_as(sandbox.project_installation_cache_path)
819
839
 
820
840
  metadata_cache.update_metadata!(target_installation_results.pod_target_installation_results || {},
@@ -873,6 +893,34 @@ module Pod
873
893
  "\n\n#{e.message}\n\n#{e.backtrace * "\n"}"
874
894
  end
875
895
 
896
+ # Runs the post integrate hooks of the installed specs and of the Podfile.
897
+ #
898
+ # @note Post integrate hooks run _after_ saving of project, so that they
899
+ # can alter it after it is written to the disk.
900
+ #
901
+ # @return [void]
902
+ #
903
+ def run_podfile_pre_integrate_hooks
904
+ UI.message '- Running pre integrate hooks' do
905
+ executed = run_podfile_pre_integrate_hook
906
+ UI.message '- Podfile' if executed
907
+ end
908
+ end
909
+
910
+ # Runs the pre integrate hook of the Podfile.
911
+ #
912
+ # @raise Raises an informative if the hooks raises.
913
+ #
914
+ # @return [Boolean] Whether the hook was run.
915
+ #
916
+ def run_podfile_pre_integrate_hook
917
+ podfile.pre_integrate!(self)
918
+ rescue => e
919
+ raise Informative, 'An error occurred while processing the pre-integrate ' \
920
+ 'hook of the Podfile.' \
921
+ "\n\n#{e.message}\n\n#{e.backtrace * "\n"}"
922
+ end
923
+
876
924
  # Runs the post install hooks of the installed specs and of the Podfile.
877
925
  #
878
926
  # @note Post install hooks run _before_ saving of project, so that they
@@ -162,9 +162,8 @@ module Pod
162
162
  Array(@podfile_requirements_by_root_name[dependency.root_name])
163
163
  end
164
164
 
165
- specifications_for_dependency(dependency, additional_requirements)
165
+ specifications_for_dependency(dependency, additional_requirements).freeze
166
166
  end
167
- @search[dependency].dup
168
167
  end
169
168
 
170
169
  # Returns the dependencies of `specification`.
@@ -175,8 +174,9 @@ module Pod
175
174
  # dependencies are being asked for.
176
175
  #
177
176
  def dependencies_for(specification)
177
+ root_name = Specification.root_name(specification.name)
178
178
  specification.all_dependencies.map do |dependency|
179
- if dependency.root_name == Specification.root_name(specification.name)
179
+ if dependency.root_name == root_name
180
180
  dependency.dup.tap { |d| d.specific_version = specification.version }
181
181
  else
182
182
  dependency
@@ -264,7 +264,7 @@ module Pod
264
264
  # @param [{String => Array<Conflict>}] conflicts the current conflicts.
265
265
  #
266
266
  def sort_dependencies(dependencies, activated, conflicts)
267
- dependencies.sort_by do |dependency|
267
+ dependencies.sort_by! do |dependency|
268
268
  name = name_for(dependency)
269
269
  [
270
270
  activated.vertex_named(name).payload ? 0 : 1,
@@ -128,6 +128,7 @@ module Pod
128
128
  #
129
129
  def public_headers(include_frameworks = false)
130
130
  public_headers = public_header_files
131
+ project_headers = project_header_files
131
132
  private_headers = private_header_files
132
133
  if public_headers.nil? || public_headers.empty?
133
134
  header_files = headers
@@ -135,7 +136,13 @@ module Pod
135
136
  header_files = public_headers
136
137
  end
137
138
  header_files += vendored_frameworks_headers if include_frameworks
138
- header_files - private_headers
139
+ header_files - project_headers - private_headers
140
+ end
141
+
142
+ # @return [Array<Pathname>] The project headers of the specification.
143
+ #
144
+ def project_headers
145
+ project_header_files
139
146
  end
140
147
 
141
148
  # @return [Array<Pathname>] The private headers of the specification.
@@ -172,6 +179,15 @@ module Pod
172
179
  end
173
180
  end
174
181
 
182
+ # @return [Array<Pathname>] The paths of the dynamic xcframework bundles
183
+ # that come shipped with the Pod.
184
+ #
185
+ def vendored_static_xcframeworks
186
+ vendored_xcframeworks.select do |path|
187
+ Xcode::XCFramework.new(spec.name, path).build_type == BuildType.static_framework
188
+ end
189
+ end
190
+
175
191
  # @return [Array<Pathname>] The paths of the static (fake) framework
176
192
  # bundles that come shipped with the Pod.
177
193
  #
@@ -191,7 +207,7 @@ module Pod
191
207
  # @param [Array<FileAccessor>] file_accessors
192
208
  # The list of all file accessors to compute.
193
209
  #
194
- # @return [Array<String>] The list of all file accessors that a target will integrate into the project.
210
+ # @return [Array<Pathname>] The list of all file accessors that a target will integrate into the project.
195
211
  #
196
212
  def self.all_files(file_accessors)
197
213
  files = [
@@ -203,10 +219,11 @@ module Pod
203
219
  file_accessors.map(&:preserve_paths),
204
220
  file_accessors.map(&:readme),
205
221
  file_accessors.map(&:resources),
222
+ file_accessors.map(&:on_demand_resources_files),
206
223
  file_accessors.map(&:source_files),
207
224
  file_accessors.map(&:module_map),
208
225
  ]
209
- files.flatten.compact.map(&:to_s).uniq
226
+ files.flatten.compact.uniq
210
227
  end
211
228
 
212
229
  # @param [Pathname] framework
@@ -229,14 +246,17 @@ module Pod
229
246
  Pathname.glob(headers_dir + '**/' + GLOB_PATTERNS[:public_header_files])
230
247
  end
231
248
 
232
- # @param [Pathname] framework
249
+ # @param [String] target_name
250
+ # The target name this .xcframework belongs to
251
+ #
252
+ # @param [Pathname] framework_path
233
253
  # The path to the .xcframework
234
254
  #
235
255
  # @return [Array<Pathname>] The paths to all the headers included in the
236
256
  # vendored xcframework
237
257
  #
238
- def self.vendored_xcframework_headers(framework)
239
- xcframework = Xcode::XCFramework.new(framework)
258
+ def self.vendored_xcframework_headers(target_name, framework_path)
259
+ xcframework = Xcode::XCFramework.new(target_name, framework_path)
240
260
  xcframework.slices.flat_map do |slice|
241
261
  vendored_frameworks_headers(slice.path)
242
262
  end
@@ -250,7 +270,7 @@ module Pod
250
270
  self.class.vendored_frameworks_headers(framework)
251
271
  end.uniq
252
272
  paths.concat Array.new(vendored_xcframeworks.flat_map do |framework|
253
- self.class.vendored_xcframework_headers(framework)
273
+ self.class.vendored_xcframework_headers(spec.name, framework)
254
274
  end)
255
275
  paths
256
276
  end
@@ -289,7 +309,7 @@ module Pod
289
309
  # that come shipped with the Pod.
290
310
  #
291
311
  def vendored_static_artifacts
292
- vendored_static_libraries + vendored_static_frameworks
312
+ vendored_static_libraries + vendored_static_frameworks + vendored_static_xcframeworks
293
313
  end
294
314
 
295
315
  # @return [Hash{String => Array<Pathname>}] A hash that describes the
@@ -314,6 +334,26 @@ module Pod
314
334
  resource_bundles.values.flatten
315
335
  end
316
336
 
337
+ # @return [Hash{String => Hash] The expanded paths of the on demand resources specified
338
+ # keyed by their tag including their category.
339
+ #
340
+ def on_demand_resources
341
+ result = {}
342
+ spec_consumer.on_demand_resources.each do |tag_name, file_patterns|
343
+ paths = expanded_paths(file_patterns[:paths],
344
+ :exclude_patterns => spec_consumer.exclude_files,
345
+ :include_dirs => true)
346
+ result[tag_name] = { :paths => paths, :category => file_patterns[:category] }
347
+ end
348
+ result
349
+ end
350
+
351
+ # @return [Array<Pathname>] The expanded paths of the on demand resources.
352
+ #
353
+ def on_demand_resources_files
354
+ on_demand_resources.values.flat_map { |v| v[:paths] }
355
+ end
356
+
317
357
  # @return [Pathname] The of the prefix header file of the specification.
318
358
  #
319
359
  def prefix_header
@@ -322,7 +362,7 @@ module Pod
322
362
  end
323
363
  end
324
364
 
325
- # @return [Pathname] The path of the auto-detected README file.
365
+ # @return [Pathname, nil] The path of the auto-detected README file.
326
366
  #
327
367
  def readme
328
368
  path_list.glob([GLOB_PATTERNS[:readme]]).first
@@ -404,7 +444,14 @@ module Pod
404
444
  paths_for_attribute(:public_header_files)
405
445
  end
406
446
 
407
- # @return [Array<Pathname>] The paths of the user-specified public header
447
+ # @return [Array<Pathname>] The paths of the user-specified project header
448
+ # files.
449
+ #
450
+ def project_header_files
451
+ paths_for_attribute(:project_header_files)
452
+ end
453
+
454
+ # @return [Array<Pathname>] The paths of the user-specified private header
408
455
  # files.
409
456
  #
410
457
  def private_header_files
@@ -51,7 +51,9 @@ module Pod
51
51
  #
52
52
  def search_paths(platform, target_name = nil, use_modular_headers = false)
53
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)
54
+ if (cached = @search_paths_cache[key])
55
+ return cached
56
+ end
55
57
  search_paths = @search_paths.select do |entry|
56
58
  matches_platform = entry[:platform] == platform.name
57
59
  matches_target = target_name.nil? || (File.basename(entry[:path]) == target_name)
@@ -23,7 +23,7 @@ module Pod
23
23
  # @param [Pathname] root @see #root
24
24
  #
25
25
  def initialize(root)
26
- root_dir = ActiveSupport::Multibyte::Unicode.normalize(root.to_s)
26
+ root_dir = root.to_s.unicode_normalize(:nfkc)
27
27
  @root = Pathname.new(root_dir)
28
28
  @glob_cache = {}
29
29
  end
@@ -64,7 +64,7 @@ module Pod
64
64
  # specifications (according to their platform) of this Pod.
65
65
  #
66
66
  def used_files
67
- FileAccessor.all_files(file_accessors)
67
+ FileAccessor.all_files(file_accessors).map(&:to_s)
68
68
  end
69
69
  end
70
70
  end
@@ -1,4 +1,5 @@
1
1
  require 'cocoapods-core/source'
2
+ require 'cocoapods/open-uri'
2
3
  require 'netrc'
3
4
  require 'set'
4
5
  require 'rest'
@@ -70,15 +71,20 @@ module Pod
70
71
  # The URL of the source.
71
72
  #
72
73
  def cdn_url?(url)
73
- if url =~ %r{^https?:\/\/}
74
- require 'typhoeus'
74
+ return unless url =~ %r{^https?:\/\/}
75
75
 
76
- response = Typhoeus.get(url + '/CocoaPods-version.yml', :netrc_file => Netrc.default_path, :netrc => :optional)
77
- response.code == 200 && begin
78
- response_hash = YAML.load(response.body) # rubocop:disable Security/YAMLLoad
79
- response_hash.is_a?(Hash) && !Source::Metadata.new(response_hash).latest_cocoapods_version.nil?
80
- end
81
- end
76
+ uri_options = {}
77
+
78
+ netrc_info = Netrc.read
79
+ netrc_host = URI.parse(url).host
80
+ credentials = netrc_info[netrc_host]
81
+ uri_options[:http_basic_authentication] = credentials if credentials
82
+
83
+ response = OpenURI.open_uri(url.chomp('/') + '/CocoaPods-version.yml', uri_options)
84
+ response_hash = YAML.load(response.read) # rubocop:disable Security/YAMLLoad
85
+ response_hash.is_a?(Hash) && !Source::Metadata.new(response_hash).latest_cocoapods_version.nil?
86
+ rescue ::OpenURI::HTTPError, SocketError
87
+ return false
82
88
  rescue => e
83
89
  raise Informative, "Couldn't determine repo type for URL: `#{url}`: #{e}"
84
90
  end
@@ -225,6 +225,12 @@ module Pod
225
225
  !resource_paths_by_config.each_value.all?(&:empty?)
226
226
  end
227
227
 
228
+ # @return [Boolean] Whether the target contains any on demand resources
229
+ #
230
+ def includes_on_demand_resources?
231
+ !on_demand_resources.empty?
232
+ end
233
+
228
234
  # @return [Boolean] Whether the target contains frameworks to be embedded into
229
235
  # the user target
230
236
  #
@@ -273,6 +279,20 @@ module Pod
273
279
  end
274
280
  end
275
281
 
282
+ # @return [Array<Pathname>] Uniqued On Demand Resources for this target.
283
+ #
284
+ # @note On Demand Resources are not separated by config as they are integrated directly into the users target via
285
+ # the resources build phase.
286
+ #
287
+ def on_demand_resources
288
+ @on_demand_resources ||= begin
289
+ pod_targets.flat_map do |pod_target|
290
+ library_file_accessors = pod_target.file_accessors.select { |fa| fa.spec.library_specification? }
291
+ library_file_accessors.flat_map(&:on_demand_resources_files)
292
+ end.uniq
293
+ end
294
+ end
295
+
276
296
  # @return [Hash{String => Array<String>}] Uniqued Resources grouped by config
277
297
  #
278
298
  def resource_paths_by_config
@@ -292,7 +312,9 @@ module Pod
292
312
  extname = File.extname(resource_path)
293
313
  if self.class.resource_extension_compilable?(extname)
294
314
  output_extname = self.class.output_extension_for_resource(extname)
295
- built_product_dir.join(File.basename(resource_path)).sub_ext(output_extname).to_s
315
+ output_path_components = Pathname(resource_path).each_filename.select { |component| File.extname(component) == '.lproj' }
316
+ output_path_components << File.basename(resource_path)
317
+ built_product_dir.join(*output_path_components).sub_ext(output_extname).to_s
296
318
  else
297
319
  resource_path
298
320
  end