cocoapods 0.39.0 → 1.0.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +261 -12
  3. data/lib/cocoapods.rb +1 -0
  4. data/lib/cocoapods/command.rb +1 -0
  5. data/lib/cocoapods/command/env.rb +66 -0
  6. data/lib/cocoapods/command/init.rb +1 -1
  7. data/lib/cocoapods/command/lib.rb +1 -1
  8. data/lib/cocoapods/command/project.rb +0 -4
  9. data/lib/cocoapods/command/repo/lint.rb +7 -6
  10. data/lib/cocoapods/command/repo/push.rb +22 -1
  11. data/lib/cocoapods/command/setup.rb +0 -24
  12. data/lib/cocoapods/command/spec/create.rb +3 -1
  13. data/lib/cocoapods/command/spec/edit.rb +14 -21
  14. data/lib/cocoapods/command/spec/env_spec.rb +53 -0
  15. data/lib/cocoapods/command/spec/lint.rb +1 -1
  16. data/lib/cocoapods/config.rb +1 -34
  17. data/lib/cocoapods/downloader.rb +9 -4
  18. data/lib/cocoapods/external_sources.rb +0 -4
  19. data/lib/cocoapods/external_sources/abstract_external_source.rb +38 -11
  20. data/lib/cocoapods/external_sources/path_source.rb +2 -2
  21. data/lib/cocoapods/gem_version.rb +2 -2
  22. data/lib/cocoapods/generator/acknowledgements.rb +1 -1
  23. data/lib/cocoapods/generator/acknowledgements/plist.rb +1 -1
  24. data/lib/cocoapods/generator/copy_resources_script.rb +28 -21
  25. data/lib/cocoapods/generator/info_plist_file.rb +34 -8
  26. data/lib/cocoapods/generator/module_map.rb +3 -18
  27. data/lib/cocoapods/generator/xcconfig/aggregate_xcconfig.rb +22 -10
  28. data/lib/cocoapods/generator/xcconfig/pod_xcconfig.rb +2 -1
  29. data/lib/cocoapods/generator/xcconfig/xcconfig_helper.rb +2 -1
  30. data/lib/cocoapods/hooks_manager.rb +3 -11
  31. data/lib/cocoapods/installer.rb +45 -25
  32. data/lib/cocoapods/installer/analyzer.rb +53 -25
  33. data/lib/cocoapods/installer/analyzer/sandbox_analyzer.rb +2 -13
  34. data/lib/cocoapods/installer/analyzer/target_inspection_result.rb +4 -0
  35. data/lib/cocoapods/installer/analyzer/target_inspector.rb +22 -19
  36. data/lib/cocoapods/installer/file_references_installer.rb +53 -6
  37. data/lib/cocoapods/installer/installation_options.rb +156 -0
  38. data/lib/cocoapods/installer/migrator.rb +1 -56
  39. data/lib/cocoapods/installer/pod_source_installer.rb +10 -8
  40. data/lib/cocoapods/installer/podfile_validator.rb +42 -1
  41. data/lib/cocoapods/installer/post_install_hooks_context.rb +19 -2
  42. data/lib/cocoapods/installer/target_installer.rb +6 -2
  43. data/lib/cocoapods/installer/target_installer/aggregate_target_installer.rb +6 -5
  44. data/lib/cocoapods/installer/target_installer/pod_target_installer.rb +82 -14
  45. data/lib/cocoapods/installer/user_project_integrator.rb +37 -16
  46. data/lib/cocoapods/installer/user_project_integrator/target_integrator.rb +14 -136
  47. data/lib/cocoapods/installer/user_project_integrator/target_integrator/xcconfig_integrator.rb +15 -22
  48. data/lib/cocoapods/project.rb +109 -19
  49. data/lib/cocoapods/resolver.rb +17 -15
  50. data/lib/cocoapods/resolver/lazy_specification.rb +4 -0
  51. data/lib/cocoapods/sandbox.rb +0 -32
  52. data/lib/cocoapods/sandbox/headers_store.rb +2 -2
  53. data/lib/cocoapods/sandbox/podspec_finder.rb +1 -1
  54. data/lib/cocoapods/sources_manager.rb +181 -50
  55. data/lib/cocoapods/target/aggregate_target.rb +17 -11
  56. data/lib/cocoapods/target/pod_target.rb +31 -4
  57. data/lib/cocoapods/user_interface.rb +32 -3
  58. data/lib/cocoapods/user_interface/error_report.rb +46 -36
  59. data/lib/cocoapods/validator.rb +132 -43
  60. metadata +164 -79
@@ -61,8 +61,8 @@ module Pod
61
61
  def integrate!
62
62
  create_workspace
63
63
  integrate_user_targets
64
- warn_about_empty_podfile
65
64
  warn_about_xcconfig_overrides
65
+ save_projects
66
66
  end
67
67
 
68
68
  #-----------------------------------------------------------------------#
@@ -91,7 +91,7 @@ module Pod
91
91
  workspace = Xcodeproj::Workspace.new_from_xcworkspace(workspace_path)
92
92
  new_file_references = file_references - workspace.file_references
93
93
  unless new_file_references.empty?
94
- workspace.file_references.concat(new_file_references)
94
+ new_file_references.each { |fr| workspace << fr }
95
95
  workspace.save_as(workspace_path)
96
96
  end
97
97
 
@@ -112,25 +112,42 @@ module Pod
112
112
  # @return [void]
113
113
  #
114
114
  def integrate_user_targets
115
- targets_to_integrate.sort_by(&:name).each do |target|
116
- TargetIntegrator.new(target).integrate!
115
+ target_integrators = targets_to_integrate.sort_by(&:name).map do |target|
116
+ TargetIntegrator.new(target)
117
117
  end
118
+
119
+ Config.instance.with_changes(:silent => true) do
120
+ deintegrator = Deintegrator.new
121
+ all_project_targets = user_projects.flat_map(&:native_targets).uniq
122
+ all_native_targets = targets_to_integrate.flat_map(&:user_targets).uniq
123
+ targets_to_deintegrate = all_project_targets - all_native_targets
124
+ targets_to_deintegrate.each do |target|
125
+ deintegrator.deintegrate_target(target)
126
+ end
127
+ end
128
+
129
+ target_integrators.each(&:integrate!)
118
130
  end
119
131
 
120
- # Warns the user if the podfile is empty.
121
- #
122
- # @note The workspace is created in any case and all the user projects
123
- # are added to it, however the projects are not integrated as
124
- # there is no way to discern between target definitions which are
125
- # empty and target definitions which just serve the purpose to
126
- # wrap other ones. This is not an issue because empty target
127
- # definitions generate empty libraries.
132
+ # Save all user projects.
128
133
  #
129
134
  # @return [void]
130
135
  #
131
- def warn_about_empty_podfile
132
- if podfile.target_definitions.values.all?(&:empty?)
133
- UI.warn '[!] The Podfile does not contain any dependencies.'
136
+ def save_projects
137
+ user_projects.each do |project|
138
+ if project.dirty?
139
+ project.save
140
+ else
141
+ # There is a bug in Xcode where the process of deleting and
142
+ # re-creating the xcconfig files used in the build
143
+ # configuration cause building the user project to fail until
144
+ # Xcode is relaunched.
145
+ #
146
+ # Touching/saving the project causes Xcode to reload these.
147
+ #
148
+ # https://github.com/CocoaPods/CocoaPods/issues/2665
149
+ FileUtils.touch(project.path + 'project.pbxproj')
150
+ end
134
151
  end
135
152
  end
136
153
 
@@ -194,8 +211,12 @@ module Pod
194
211
  targets.map(&:user_project_path).compact.uniq
195
212
  end
196
213
 
214
+ def user_projects
215
+ targets.map(&:user_project).compact.uniq
216
+ end
217
+
197
218
  def targets_to_integrate
198
- targets.reject { |target| target.target_definition.empty? }
219
+ targets.reject { |target| target.target_definition.abstract? }
199
220
  end
200
221
 
201
222
  # Prints a warning informing the user that a build configuration of
@@ -38,34 +38,12 @@ module Pod
38
38
  #
39
39
  def integrate!
40
40
  UI.section(integration_message) do
41
- # TODO: refactor into Xcodeproj https://github.com/CocoaPods/Xcodeproj/issues/202
42
- project_is_dirty = [
43
- XCConfigIntegrator.integrate(target, native_targets),
44
- update_to_cocoapods_0_34,
45
- update_to_cocoapods_0_37_1,
46
- update_to_cocoapods_0_39,
47
- unless native_targets_to_integrate.empty?
48
- add_pods_library
49
- add_embed_frameworks_script_phase
50
- add_copy_resources_script_phase
51
- add_check_manifest_lock_script_phase
52
- true
53
- end,
54
- ].any?
41
+ XCConfigIntegrator.integrate(target, native_targets)
55
42
 
56
- if project_is_dirty
57
- user_project.save
58
- else
59
- # There is a bug in Xcode where the process of deleting and
60
- # re-creating the xcconfig files used in the build
61
- # configuration cause building the user project to fail until
62
- # Xcode is relaunched.
63
- #
64
- # Touching/saving the project causes Xcode to reload these.
65
- #
66
- # https://github.com/CocoaPods/CocoaPods/issues/2665
67
- FileUtils.touch(user_project.path + 'project.pbxproj')
68
- end
43
+ add_pods_library
44
+ add_embed_frameworks_script_phase
45
+ add_copy_resources_script_phase
46
+ add_check_manifest_lock_script_phase
69
47
  end
70
48
  end
71
49
 
@@ -80,85 +58,6 @@ module Pod
80
58
  # @!group Integration steps
81
59
  #---------------------------------------------------------------------#
82
60
 
83
- # Fixes the paths of the copy resource scripts.
84
- #
85
- # @return [Bool] whether any changes to the project were made.
86
- #
87
- # @todo This can be removed for CocoaPods 1.0
88
- #
89
- def update_to_cocoapods_0_34
90
- phases = native_targets.map do |target|
91
- target.shell_script_build_phases.select do |bp|
92
- bp.name == 'Copy Pods Resources'
93
- end
94
- end.flatten
95
-
96
- script_path = target.copy_resources_script_relative_path
97
- shell_script = %("#{script_path}"\n)
98
- changes = false
99
- phases.each do |phase|
100
- unless phase.shell_script == shell_script
101
- phase.shell_script = shell_script
102
- changes = true
103
- end
104
- end
105
- changes
106
- end
107
-
108
- # Removes the embed frameworks phase for target types.
109
- #
110
- # @return [Bool] whether any changes to the project were made.
111
- #
112
- # @todo This can be removed for CocoaPods 1.0
113
- #
114
- def update_to_cocoapods_0_37_1
115
- targets_to_embed = native_targets.select do |target|
116
- EMBED_FRAMEWORK_TARGET_TYPES.include?(target.symbol_type)
117
- end
118
- (native_targets - targets_to_embed).any? do |native_target|
119
- remove_embed_frameworks_script_phase(native_target)
120
- end
121
- end
122
-
123
- # Adds the embed frameworks script when integrating as a static library.
124
- #
125
- # @return [Bool] whether any changes to the project were made.
126
- #
127
- # @todo This can be removed for CocoaPods 1.0
128
- #
129
- def update_to_cocoapods_0_39
130
- targets_to_embed = native_targets.select do |target|
131
- EMBED_FRAMEWORK_TARGET_TYPES.include?(target.symbol_type)
132
- end
133
- requires_update = targets_to_embed.any? do |target|
134
- !target.shell_script_build_phases.find { |bp| bp.name == EMBED_FRAMEWORK_PHASE_NAME }
135
- end
136
- if requires_update
137
- targets_to_embed.each do |native_target|
138
- add_embed_frameworks_script_phase_to_target(native_target)
139
- end
140
- end
141
-
142
- frameworks = user_project.frameworks_group
143
- native_targets.each do |native_target|
144
- build_phase = native_target.frameworks_build_phase
145
-
146
- product_ref = frameworks.files.find { |f| f.path == target.product_name }
147
- if product_ref
148
- build_file = build_phase.build_file(product_ref)
149
- if build_file &&
150
- build_file.settings.is_a?(Hash) &&
151
- build_file.settings['ATTRIBUTES'].is_a?(Array) &&
152
- build_file.settings['ATTRIBUTES'].include?('Weak')
153
- build_file.settings = nil
154
- requires_update = true
155
- end
156
- end
157
- end
158
-
159
- requires_update
160
- end
161
-
162
61
  # Adds spec product reference to the frameworks build phase of the
163
62
  # {TargetDefinition} integration libraries. Adds a file reference to
164
63
  # the frameworks group of the project and adds it to the frameworks
@@ -168,7 +67,7 @@ module Pod
168
67
  #
169
68
  def add_pods_library
170
69
  frameworks = user_project.frameworks_group
171
- native_targets_to_integrate.each do |native_target|
70
+ native_targets.each do |native_target|
172
71
  build_phase = native_target.frameworks_build_phase
173
72
 
174
73
  # Find and delete possible reference for the other product type
@@ -209,13 +108,10 @@ module Pod
209
108
  #
210
109
  # @param [PBXNativeTarget] native_target
211
110
  #
212
- # @return [Bool] whether any changes to the project were made.
213
- #
214
111
  def remove_embed_frameworks_script_phase(native_target)
215
112
  embed_build_phase = native_target.shell_script_build_phases.find { |bp| bp.name == EMBED_FRAMEWORK_PHASE_NAME }
216
- return false unless embed_build_phase.present?
113
+ return unless embed_build_phase.present?
217
114
  native_target.build_phases.delete(embed_build_phase)
218
- true
219
115
  end
220
116
 
221
117
  # Adds a shell script build phase responsible to copy the resources
@@ -226,7 +122,7 @@ module Pod
226
122
  #
227
123
  def add_copy_resources_script_phase
228
124
  phase_name = 'Copy Pods Resources'
229
- native_targets_to_integrate.each do |native_target|
125
+ native_targets.each do |native_target|
230
126
  phase = create_or_update_build_phase(native_target, phase_name)
231
127
  script_path = target.copy_resources_script_relative_path
232
128
  phase.shell_script = %("#{script_path}"\n)
@@ -244,9 +140,9 @@ module Pod
244
140
  #
245
141
  def add_check_manifest_lock_script_phase
246
142
  phase_name = 'Check Pods Manifest.lock'
247
- native_targets_to_integrate.each do |native_target|
143
+ native_targets.each do |native_target|
248
144
  phase = create_or_update_build_phase(native_target, phase_name)
249
- native_target.build_phases.unshift(phase).uniq!
145
+ native_target.build_phases.unshift(phase).uniq! unless native_target.build_phases.first == phase
250
146
  phase.shell_script = <<-SH.strip_heredoc
251
147
  diff "${PODS_ROOT}/../Podfile.lock" "${PODS_ROOT}/Manifest.lock" > /dev/null
252
148
  if [[ $? != 0 ]] ; then
@@ -261,14 +157,14 @@ module Pod
261
157
 
262
158
  private
263
159
 
264
- # @!group Private helpers
160
+ # @!group Private Helpers
265
161
  #---------------------------------------------------------------------#
266
162
 
267
163
  # @return [Array<PBXNativeTarget>] The list of all the targets that
268
164
  # match the given target.
269
165
  #
270
166
  def native_targets
271
- @native_targets ||= target.user_targets(user_project)
167
+ @native_targets ||= target.user_targets
272
168
  end
273
169
 
274
170
  # @return [Array<PBXNativeTarget>] The list of all the targets that
@@ -276,36 +172,18 @@ module Pod
276
172
  # directory / product bundle.
277
173
  #
278
174
  def native_targets_to_embed_in
279
- native_targets_to_integrate.select do |target|
175
+ native_targets.select do |target|
280
176
  EMBED_FRAMEWORK_TARGET_TYPES.include?(target.symbol_type)
281
177
  end
282
178
  end
283
179
 
284
- # @return [Array<PBXNativeTarget>] The list of the targets
285
- # that have not been integrated by past installations
286
- # of
287
- #
288
- def native_targets_to_integrate
289
- unless @native_targets_to_integrate
290
- @native_targets_to_integrate = native_targets.reject do |native_target|
291
- native_target.frameworks_build_phase.files.any? do |build_file|
292
- file_ref = build_file.file_ref
293
- file_ref &&
294
- file_ref.isa == 'PBXFileReference' &&
295
- file_ref.display_name == target.product_name
296
- end
297
- end
298
- end
299
- @native_targets_to_integrate
300
- end
301
-
302
180
  # Read the project from the disk to ensure that it is up to date as
303
181
  # other TargetIntegrators might have modified it.
304
182
  #
305
183
  # @return [Project]
306
184
  #
307
185
  def user_project
308
- @user_project ||= Xcodeproj::Project.open(target.user_project_path)
186
+ target.user_project
309
187
  end
310
188
 
311
189
  # @return [Specification::Consumer] the consumer for the specifications.
@@ -15,17 +15,13 @@ module Pod
15
15
  # The native targets associated which should be integrated
16
16
  # with the Pod bundle.
17
17
  #
18
- # @return [Bool] whether any changes to the project were made.
19
- #
20
18
  def self.integrate(pod_bundle, targets)
21
- changes = false
22
19
  targets.each do |target|
23
20
  target.build_configurations.each do |config|
24
- changes = true if update_to_cocoapods_0_34(pod_bundle, targets)
25
- changes = true if set_target_xcconfig(pod_bundle, target, config)
21
+ update_to_cocoapods_0_34(pod_bundle, targets)
22
+ set_target_xcconfig(pod_bundle, target, config)
26
23
  end
27
24
  end
28
- changes
29
25
  end
30
26
 
31
27
  private
@@ -42,13 +38,10 @@ module Pod
42
38
  # @param [Array<XcodeProj::PBXNativeTarget>] targets
43
39
  # The native targets.
44
40
  #
45
- # @return [Bool] whether any changes to the project were made.
46
- #
47
41
  # @todo This can be removed for CocoaPods 1.0
48
42
  #
49
43
  def self.update_to_cocoapods_0_34(pod_bundle, targets)
50
44
  sandbox = pod_bundle.sandbox
51
- changes = false
52
45
  targets.map(&:project).uniq.each do |project|
53
46
  file_refs = project.files.select do |file_ref|
54
47
  path = file_ref.path.to_s
@@ -64,10 +57,7 @@ module Pod
64
57
  file_ref.remove_from_project
65
58
  end
66
59
  end
67
-
68
- changes = true unless file_refs.empty?
69
60
  end
70
- changes
71
61
  end
72
62
 
73
63
  # Creates a file reference to the xcconfig generated by
@@ -83,28 +73,31 @@ module Pod
83
73
  # @param [Xcodeproj::XCBuildConfiguration] config
84
74
  # The build configuration.
85
75
  #
86
- # @return [Boolean] Indicates whether or not any changes were made.
87
- #
88
76
  def self.set_target_xcconfig(pod_bundle, target, config)
89
77
  path = pod_bundle.xcconfig_relative_path(config.name)
90
78
  group = config.project['Pods'] || config.project.new_group('Pods')
91
79
  file_ref = group.files.find { |f| f.path == path }
92
- if config.base_configuration_reference &&
93
- config.base_configuration_reference != file_ref
94
- unless xcconfig_includes_target_xcconfig?(config.base_configuration_reference, path)
80
+ existing = config.base_configuration_reference
81
+
82
+ set_base_configuration_reference = ->() do
83
+ file_ref ||= group.new_file(path)
84
+ config.base_configuration_reference = file_ref
85
+ end
86
+
87
+ if existing && existing != file_ref
88
+ if existing.real_path.to_path.start_with?(pod_bundle.sandbox.root.to_path << '/')
89
+ set_base_configuration_reference.call
90
+ elsif !xcconfig_includes_target_xcconfig?(config.base_configuration_reference, path)
95
91
  UI.warn 'CocoaPods did not set the base configuration of your ' \
96
92
  'project because your project already has a custom ' \
97
93
  'config set. In order for CocoaPods integration to work at ' \
98
94
  'all, please either set the base configurations of the target ' \
99
95
  "`#{target.name}` to `#{path}` or include the `#{path}` in your " \
100
- 'build configuration.'
96
+ "build configuration (#{UI.path(existing.real_path)})."
101
97
  end
102
98
  elsif config.base_configuration_reference.nil? || file_ref.nil?
103
- file_ref ||= group.new_file(path)
104
- config.base_configuration_reference = file_ref
105
- return true
99
+ set_base_configuration_reference.call
106
100
  end
107
- false
108
101
  end
109
102
 
110
103
  private
@@ -1,4 +1,5 @@
1
1
  require 'xcodeproj'
2
+ require 'active_support/core_ext/string/inflections'
2
3
 
3
4
  module Pod
4
5
  # The Pods project.
@@ -20,6 +21,7 @@ module Pod
20
21
  super(path, skip_initialization, object_version)
21
22
  @support_files_group = new_group('Targets Support Files')
22
23
  @refs_by_absolute_path = {}
24
+ @variant_groups_by_path_and_name = {}
23
25
  @pods = new_group('Pods')
24
26
  @development_pods = new_group('Development Pods')
25
27
  self.symroot = LEGACY_BUILD_ROOT
@@ -178,24 +180,13 @@ module Pod
178
180
  # @return [PBXFileReference] The new file reference.
179
181
  #
180
182
  def add_file_reference(absolute_path, group, reflect_file_system_structure = false)
181
- file_path_name = Pathname.new(absolute_path)
182
- unless file_path_name.absolute?
183
- raise ArgumentError, "Paths must be absolute #{absolute_path}"
184
- end
183
+ file_path_name = absolute_path.is_a?(Pathname) ? absolute_path : Pathname.new(absolute_path)
184
+ group = group_for_path_in_group(file_path_name, group, reflect_file_system_structure)
185
185
 
186
- if reflect_file_system_structure
187
- relative_path = file_path_name.relative_path_from(group.real_path)
188
- relative_dir = relative_path.dirname
189
- relative_dir.each_filename do|name|
190
- next if name == '.'
191
- group = group[name] || group.new_group(name, name)
192
- end
193
- end
194
-
195
- if ref = reference_for_path(absolute_path)
186
+ if ref = reference_for_path(file_path_name.realpath)
196
187
  ref
197
188
  else
198
- ref = group.new_file(absolute_path)
189
+ ref = group.new_file(file_path_name.realpath)
199
190
  @refs_by_absolute_path[absolute_path.to_s] = ref
200
191
  end
201
192
  end
@@ -250,18 +241,29 @@ module Pod
250
241
  #
251
242
  def add_build_configuration(name, type)
252
243
  build_configuration = super
253
- values = ["#{name.gsub(/[^a-zA-Z0-9_]/, '_').sub(/(^[0-9])/, '_\1').upcase}=1"]
254
244
  settings = build_configuration.build_settings
255
- definitions = Array(settings['GCC_PREPROCESSOR_DEFINITIONS'])
256
- values.each do |value|
245
+ definitions = settings['GCC_PREPROCESSOR_DEFINITIONS'] || ['$(inherited)']
246
+ defines = [defininition_for_build_configuration(name)]
247
+ defines << 'DEBUG' if type == :debug
248
+ defines.each do |define|
249
+ value = "#{define}=1"
257
250
  unless definitions.include?(value)
258
- definitions << value
251
+ definitions.unshift(value)
259
252
  end
260
253
  end
261
254
  settings['GCC_PREPROCESSOR_DEFINITIONS'] = definitions
262
255
  build_configuration
263
256
  end
264
257
 
258
+ # @param [String] name
259
+ # The name of the build configuration.
260
+ #
261
+ # @return [String] The preprocessor definition to set for the configuration.
262
+ #
263
+ def defininition_for_build_configuration(name)
264
+ "POD_CONFIGURATION_#{name.underscore}".gsub(/[^a-zA-Z0-9_]/, '_').upcase
265
+ end
266
+
265
267
  private
266
268
 
267
269
  # @!group Private helpers
@@ -272,6 +274,94 @@ module Pod
272
274
  #
273
275
  attr_reader :refs_by_absolute_path
274
276
 
277
+ # @return [Hash{[Pathname, String] => PBXVariantGroup}] The variant groups
278
+ # grouped by absolute path of parent dir and name.
279
+ #
280
+ attr_reader :variant_groups_by_path_and_name
281
+
282
+ # Returns the group for an absolute file path in another group.
283
+ # Creates subgroups to reflect the file system structure if
284
+ # reflect_file_system_structure is set to true.
285
+ # Makes a variant group if the path points to a localized file inside a
286
+ # *.lproj directory. To support Apple Base Internationalization, the same
287
+ # variant group is returned for interface files and strings files with
288
+ # the same name.
289
+ #
290
+ # @param [Pathname] absolute_pathname
291
+ # The pathname of the file to get the group for.
292
+ #
293
+ # @param [PBXGroup] group
294
+ # The parent group used as the base of the relative path.
295
+ #
296
+ # @param [Bool] reflect_file_system_structure
297
+ # Whether group structure should reflect the file system structure.
298
+ # If yes, where needed, intermediate groups are created, similar to
299
+ # how mkdir -p operates.
300
+ #
301
+ # @return [PBXGroup] The appropriate group for the filepath.
302
+ # Can be PBXVariantGroup, if the file is localized.
303
+ #
304
+ def group_for_path_in_group(absolute_pathname, group, reflect_file_system_structure)
305
+ unless absolute_pathname.absolute?
306
+ raise ArgumentError, "Paths must be absolute #{absolute_pathname}"
307
+ end
308
+
309
+ relative_pathname = absolute_pathname.relative_path_from(group.real_path)
310
+ relative_dir = relative_pathname.dirname
311
+ lproj_regex = /\.lproj/i
312
+
313
+ # Add subgroups for directories, but treat .lproj as a file
314
+ if reflect_file_system_structure
315
+ relative_dir.each_filename do|name|
316
+ break if name.to_s =~ lproj_regex
317
+ next if name == '.'
318
+ group = group[name] || group.new_group(name, name)
319
+ end
320
+ end
321
+
322
+ # Turn files inside .lproj directories into a variant group
323
+ if relative_dir.basename.to_s =~ lproj_regex
324
+ group_name = variant_group_name(absolute_pathname)
325
+ lproj_parent_dir = absolute_pathname.dirname.dirname
326
+ group = @variant_groups_by_path_and_name[[lproj_parent_dir, group_name]] ||
327
+ group.new_variant_group(group_name, lproj_parent_dir)
328
+ @variant_groups_by_path_and_name[[lproj_parent_dir, group_name]] ||= group
329
+ end
330
+
331
+ group
332
+ end
333
+
334
+ # Returns the name to be used for a the variant group for a file at a given path.
335
+ # The path must be localized (within an *.lproj directory).
336
+ #
337
+ # @param [Pathname] The localized path to get a variant group name for.
338
+ #
339
+ # @return [String] The variant group name.
340
+ #
341
+ def variant_group_name(path)
342
+ unless path.to_s.downcase.include?('.lproj/')
343
+ raise ArgumentError, 'Only localized resources can be added to variant groups.'
344
+ end
345
+
346
+ # When using Base Internationalization for XIBs and Storyboards a strings
347
+ # file is generated with the same name as the XIB/Storyboard in each .lproj
348
+ # directory:
349
+ # Base.lproj/MyViewController.xib
350
+ # fr.lproj/MyViewController.strings
351
+ #
352
+ # In this scenario we want the variant group to be the same as the XIB or Storyboard.
353
+ #
354
+ # Base Internationalization: https://developer.apple.com/library/ios/documentation/MacOSX/Conceptual/BPInternational/InternationalizingYourUserInterface/InternationalizingYourUserInterface.html
355
+ if path.extname.downcase == '.strings'
356
+ %w(.xib .storyboard).each do |extension|
357
+ possible_interface_file = path.dirname.dirname + 'Base.lproj' + path.basename.sub_ext(extension)
358
+ return possible_interface_file.basename.to_s if possible_interface_file.exist?
359
+ end
360
+ end
361
+
362
+ path.basename.to_s
363
+ end
364
+
275
365
  #-------------------------------------------------------------------------#
276
366
  end
277
367
  end