cocoapods 1.10.1 → 1.11.0.rc.1

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