cocoapods-mapfile 0.2.5 → 0.2.7.0

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.
@@ -11,45 +11,48 @@ module HMap
11
11
  def project_buckets
12
12
  h_name = File.basename(path)
13
13
  h_dir = File.dirname(path)
14
- [BucketStr.new(h_name, "#{h_dir}/", h_name)]
14
+ { h_name => ["#{h_dir}/", h_name] }
15
15
  end
16
16
 
17
17
  def module_buckets(moudle_name)
18
18
  h_name = File.basename(path)
19
19
  module_p = "#{moudle_name}/"
20
- [BucketStr.new(h_name, module_p, h_name)]
20
+ { h_name => [module_p, h_name] }
21
21
  end
22
22
 
23
23
  def full_module_buckets(moudle_name)
24
24
  h_name = File.basename(path)
25
25
  h_dir = File.dirname(path)
26
26
  module_k = "#{moudle_name}/#{h_name}"
27
- [BucketStr.new(module_k, "#{h_dir}/", h_name)]
27
+ { module_k => ["#{h_dir}/", h_name] }
28
28
  end
29
29
 
30
30
  def project_buckets_extra
31
31
  h_name = File.basename(path)
32
32
  h_dir = File.dirname(path)
33
- buckets = []
34
- buckets << BucketStr.new(h_name, "#{h_dir}/", h_name) unless extra_keys.include?(h_name)
35
- buckets + extra_keys.map { |key| BucketStr.new(key, "#{h_dir}/", h_name) }
33
+ buckets = {}
34
+ buckets[h_name] = ["#{h_dir}/", h_name] unless extra_keys.include?(h_name)
35
+ extra_keys.each { |key| buckets[key] = ["#{h_dir}/", h_name] }
36
+ buckets
36
37
  end
37
38
 
38
39
  def module_buckets_extra(moudle_name)
39
40
  h_name = File.basename(path)
40
41
  module_p = "#{moudle_name}/"
41
- buckets = []
42
- buckets << BucketStr.new(h_name, module_p, h_name) unless extra_keys.include?(h_name)
43
- buckets + extra_keys.map { |key| BucketStr.new(key, module_p, h_name) }
42
+ buckets = {}
43
+ buckets[h_name] = [module_p, h_name] unless extra_keys.include?(h_name)
44
+ extra_keys.each { |key| buckets[key] = [module_p, h_name] }
45
+ buckets
44
46
  end
45
47
 
46
48
  def full_module_buckets_extra(moudle_name)
47
49
  h_name = File.basename(path)
48
50
  h_dir = File.dirname(path)
49
51
  module_k = "#{moudle_name}/#{h_name}"
50
- buckets = []
51
- buckets << BucketStr.new(module_k, "#{h_dir}/", h_name) unless extra_keys.include?(module_k)
52
- buckets + extra_keys.map { |key| BucketStr.new(key, "#{h_dir}/", h_name) }
52
+ buckets = {}
53
+ buckets[module_k] = ["#{h_dir}/", h_name] unless extra_keys.include?(module_k)
54
+ extra_keys.each { |key| buckets[key] = ["#{h_dir}/", h_name] }
55
+ buckets
53
56
  end
54
57
  end
55
58
  end
@@ -1,15 +1,19 @@
1
1
  module HMap
2
2
  module PBXHelper
3
+ PBX_GROUP = '<group>'.freeze
4
+ PBX_SOURCE_ROOT = 'SOURCE_ROOT'.freeze
5
+ private_constant :PBX_GROUP, :PBX_SOURCE_ROOT
3
6
  def self.get_groups(xct)
4
7
  groups = xct.referrers.select { |e| e.is_a?(Constants::PBXGroup) } || []
5
8
  groups += groups.flat_map { |g| get_groups(g) }
6
- groups.compact.reverse
9
+ groups.compact
7
10
  end
8
11
 
9
12
  def self.group_paths(xct)
10
- ps = get_groups(xct).map do |g|
13
+ gs = get_groups(xct).reverse
14
+ ps = gs.map do |g|
11
15
  s_hash = g.instance_variable_get('@simple_attributes_hash')
12
- s_hash['path'] unless s_hash.nil?
16
+ s_hash['path'] unless s_hash.nil? && s_hash['sourceTree'] == PBX_GROUP
13
17
  end.compact
14
18
  File.join(*ps)
15
19
  end
@@ -46,8 +50,10 @@ module HMap
46
50
  p_path = File.dirname(project.path)
47
51
  project.root_object.project_references.map do |sp|
48
52
  ff = sp[:project_ref]
49
- path = ff.instance_variable_get('@simple_attributes_hash')['path'] || ''
50
- full_path = File.join(p_path, path)
53
+ f_hash = ff.instance_variable_get('@simple_attributes_hash')
54
+ g_path = PBXHelper.group_paths(ff) if f_hash['sourceTree'] == PBX_GROUP
55
+ path = f_hash['path'] || ''
56
+ full_path = File.join(p_path, g_path || '', path)
51
57
  Xcodeproj::Project.open(full_path)
52
58
  end << project
53
59
  end
@@ -39,7 +39,7 @@ module HMap
39
39
  end
40
40
 
41
41
  def project_build_settings(project_path)
42
- targets = xcodebuild(project_path) || []
42
+ targets = xcodebuild_project(project_path) || []
43
43
  targets.first['buildSettings']
44
44
  end
45
45
 
@@ -121,7 +121,7 @@ module HMap
121
121
  command += %w[-json -showBuildSettings]
122
122
  results = Executable.execute_command('xcodebuild', command, false)
123
123
  index = results.index(/"action"/m)
124
- reg = results[index..-1]
124
+ reg = results[index..]
125
125
  reg = "[\n{\n#{reg}"
126
126
  JSON.parse(reg) unless results.nil?
127
127
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'hmap/xc/target/target_vfs'
3
4
 
4
5
  module HMap
@@ -63,13 +64,7 @@ module HMap
63
64
  end
64
65
 
65
66
  def symlink_to(path, need_platform)
66
- return unless path.exist?
67
-
68
- filepath = Pathname.new(hmap_filepath(need_platform))
69
- filepath.dirname.mkpath unless filepath.exist?
70
- return if File.identical?(filepath, path)
71
-
72
- filepath.make_symlink(path)
67
+ Utils.file_symlink_to(path, Pathname.new(hmap_filepath(need_platform)))
73
68
  end
74
69
 
75
70
  def write(data, need_platform)
@@ -42,35 +42,38 @@ module HMap
42
42
  def xcconfig_paths
43
43
  return @xcconfig_paths if defined?(@xcconfig_paths)
44
44
 
45
- @xcconfig_paths = target.build_configuration_list.build_configurations.flat_map do |configuration|
45
+ @xcconfig_paths = target.build_configurations.flat_map do |configuration|
46
46
  if configuration.is_a?(Constants::XCBuildConfiguration)
47
47
  bcr = configuration.base_configuration_reference
48
48
  unless bcr.nil?
49
49
  s_path = PBXHelper.group_paths(bcr)
50
50
  x = bcr.instance_variable_get('@simple_attributes_hash')['path'] || ''
51
- File.expand_path File.join(project.project_dir, s_path, x)
51
+ path = File.expand_path(File.join(project.project_dir, s_path, x))
52
+ xc = XCConfig.new(path)
53
+ inc = xc.includes_paths
54
+ path if inc.empty? || project.workspace.xcconfig_paths.none? { |pa| inc.include?(pa) }
52
55
  end
53
56
  end
54
57
  end.compact
55
- # @xcconfig_paths = TargetConfiguration.new_from_xc(target, project.project_dir).map do |c|
56
- # c.xcconfig_path unless c.xcconfig_path.nil?
57
- # end.compact
58
- # @xcconfig_paths = bc.map(&:xcconfig_path).compact
59
- # xc_type = 'XCConfigurationList'
60
- # xcs = target.build_configuration_list.build_configurations
61
- # @xcconfig_paths = xcs.map do |xc|
62
- # if xc.isa == xc_type
63
- # xc_path = xc.instance_variable_get('@simple_attributes_hash')['path'] || ''
64
- # { xc => File.join(project.project_dir, xc_path) }
65
- # else
66
- # # ab_path = Pathname(project.project_dir + "hmap-#{name}.#{xc.name}.xcconfig")
67
- # # File.new(ab_path, 'w') unless ab_path.exist?
68
- # # xc_ref = target.project.new_file(ab_path)
69
- # # xc.base_configuration_reference = xc_ref
70
- # # target.project.save
71
- # # ab_path
58
+
59
+ # @xcconfig_paths = target.build_configuration_list.build_configurations.flat_map do |configuration|
60
+ # if configuration.is_a?(Constants::XCBuildConfiguration)
61
+ # bcr = configuration.base_configuration_reference
62
+ # # if bcr.nil?
63
+ # # ab_path = Pathname(project.project_dir + "hmap-#{target_name}.#{configuration.name}.xcconfig")
64
+ # # File.new(ab_path, 'w') unless ab_path.exist?
65
+ # # xc_ref = target.project.new_file(ab_path)
66
+ # # configuration.base_configuration_reference = xc_ref
67
+ # # target.project.save
68
+ # # ab_path
69
+ # # else
70
+ # unless bcr.nil?
71
+ # s_path = PBXHelper.group_paths(bcr)
72
+ # x = bcr.instance_variable_get('@simple_attributes_hash')['path'] || ''
73
+ # File.expand_path File.join(project.project_dir, s_path, x)
74
+ # end
72
75
  # end
73
- # end
76
+ # end.compact
74
77
  end
75
78
  end
76
79
  end
@@ -1,9 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HMap
4
- class Target
4
+ class Target
5
5
  class TargetContext < Context
6
6
  attr_reader :product_name
7
+
7
8
  def initialize(build_root, temp_dir, hmap_root, temp_name, build_dir, product_name, full_product_name, defines_modules, build_as_framework_swift)
8
9
  super(build_root, temp_dir, hmap_root, temp_name, build_dir)
9
10
  @full_product_name = full_product_name
@@ -11,15 +12,15 @@ module HMap
11
12
  @defines_modules = defines_modules
12
13
  @build_as_framework_swift = build_as_framework_swift
13
14
  end
14
-
15
+
15
16
  def build_as_framework_swift?
16
17
  @build_as_framework_swift
17
18
  end
18
-
19
+
19
20
  def defines_modules?
20
21
  @defines_modules
21
22
  end
22
-
23
+
23
24
  def build_path(platform)
24
25
  path = super(platform)
25
26
  File.join(path, @full_product_name)
@@ -16,29 +16,50 @@ module HMap
16
16
  define_method(:all_non_framework_target_headers) do
17
17
  return if build_as_framework?
18
18
 
19
+ return @all_non_framework_target_headers if defined? @all_non_framework_target_headers
20
+
19
21
  p_h = public_entrys + private_entrys
20
- p_h.flat_map { |entry| entry.full_module_buckets(product_name) }
22
+ @all_non_framework_target_headers = p_h.inject({}) do |sum, entry|
23
+ sum.merge!(entry.full_module_buckets(product_name)) { |_, v1, _| v1 }
24
+ end
21
25
  end
22
26
 
23
27
  # all_targets include header full module path
24
28
  define_method(:all_target_headers) do
29
+ return @all_target_headers if defined? @all_target_headers
30
+
25
31
  p_h = public_entrys + private_entrys
26
- p_h.flat_map do |entry|
27
- entry.full_module_buckets_extra(product_name) + entry.module_buckets(product_name)
32
+ @all_target_headers = p_h.inject({}) do |sum, entry|
33
+ sum.merge!(entry.module_buckets(product_name)) { |_, v1, _| v1 }
34
+ sum.merge!(entry.full_module_buckets(product_name)) { |_, v1, _| v1 }
28
35
  end
29
36
  end
30
37
 
31
38
  define_method(:project_headers) do
32
- all_target_headers + project_entrys.flat_map(&:project_buckets_extra)
39
+ return @project_headers if defined? @project_headers
40
+
41
+ p_h = public_entrys + private_entrys
42
+ hs = p_h.inject({}) do |sum, entry|
43
+ sum.merge!(entry.module_buckets(product_name)) { |_, v1, _| v1 }
44
+ end
45
+ @project_headers = project_entrys.inject(hs) do |sum, entry|
46
+ sum.merge!(entry.project_buckets_extra) { |_, v1, _| v1 }
47
+ end
33
48
  end
34
49
 
35
50
  define_method(:own_target_headers) do
51
+ return @own_target_headers if defined? @own_target_headers
52
+
36
53
  headers = public_entrys + private_entrys
37
- headers = headers.flat_map { |entry| entry.module_buckets(product_name) }
38
- headers + project_entrys.flat_map do |entry|
39
- entry.project_buckets + entry.full_module_buckets(product_name)
54
+ hs = headers.inject({}) do |sum, entry|
55
+ sum.merge!(entry.module_buckets(product_name)) { |_, v1, _| v1 }
56
+ end
57
+ @own_target_headers = project_entrys.inject(hs) do |sum, entry|
58
+ sum.merge!(entry.project_buckets) { |_, v1, _| v1 }
59
+ sum.merge!(entry.full_module_buckets(product_name)) { |_, v1, _| v1 }
40
60
  end
41
61
  end
62
+
42
63
  def build_root
43
64
  project.build_root
44
65
  end
@@ -52,7 +73,10 @@ module HMap
52
73
  end
53
74
 
54
75
  def product_name
55
- target.product_name.gsub(/[-]/, '_')
76
+ product_name = target.build_settings(target.build_configurations.first.name)['PRODUCT_NAME']
77
+ return target_name.gsub(/-/, '_') if product_name.nil? || product_name.include?('TARGET_NAME')
78
+
79
+ product_name
56
80
  end
57
81
 
58
82
  def full_product_name
@@ -70,13 +94,14 @@ module HMap
70
94
  def build_dir
71
95
  return @build_dir if defined?(@build_dir)
72
96
 
73
- b_d = xcconfig_paths.any? do |path|
74
- xc = Xcodeproj::Config.new(path)
75
- !xc.attributes[Constants::CONFIGURATION_BUILD_DIR].nil?
76
- end
97
+ b_d = xcconfig_paths.none? { |path| XCConfig.new(path).attributes[Constants::CONFIGURATION_BUILD_DIR].nil? }
77
98
  @build_dir = target_name if b_d
78
99
  end
79
100
 
101
+ def app_target?
102
+ Xcodeproj::Constants::PRODUCT_UTI_EXTENSIONS[target.symbol_type] == 'app'
103
+ end
104
+
80
105
  def build_as_framework?
81
106
  PBXHelper.build_as_framework?(target)
82
107
  end
@@ -59,7 +59,6 @@ module HMap
59
59
  end
60
60
  end
61
61
 
62
-
63
62
  # A collection of Each TargetVFSEntry
64
63
  class TargetVFS
65
64
  def initialize(public_headers, private_headers, platforms, setting)
@@ -0,0 +1,295 @@
1
+ module HMap
2
+ # This class holds the data for a Xcode build settings file (xcconfig) and
3
+ # provides support for serialization.
4
+ #
5
+ class XCConfig
6
+ require 'set'
7
+
8
+ KEY_VALUE_PATTERN = /
9
+ (
10
+ [^=\[]+ # Any char, but not an assignment operator
11
+ # or subscript (non-greedy)
12
+ (?: # One or multiple conditional subscripts
13
+ \[
14
+ [^\]]* # The subscript key
15
+ (?:
16
+ = # The subscript comparison operator
17
+ [^\]]* # The subscript value
18
+ )?
19
+ \]
20
+ )*
21
+ )
22
+ \s* # Whitespaces after the key (needed because subscripts
23
+ # always end with ']')
24
+ = # The assignment operator
25
+ (.*) # The value
26
+ /x
27
+ private_constant :KEY_VALUE_PATTERN
28
+
29
+ INHERITED = %w($(inherited) ${inherited}).freeze
30
+ private_constant :INHERITED
31
+
32
+ INHERITED_REGEXP = Regexp.union(INHERITED)
33
+ private_constant :INHERITED_REGEXP
34
+
35
+ # @return [Hash{String => String}] The attributes of the settings file
36
+ # excluding frameworks, weak_framework and libraries.
37
+ #
38
+ attr_accessor :attributes
39
+
40
+ # @return [Array] The list of the configuration files included by this
41
+ # configuration file (`#include "SomeConfig"`).
42
+ #
43
+ attr_accessor :includes_paths
44
+
45
+ # @return [Hash{Symbol => Set<String>}] The other linker flags by key.
46
+ # Xcodeproj handles them in a dedicated way to prevent duplication
47
+ # of the libraries and of the frameworks.
48
+ #
49
+ def initialize(xcconfig_hash_or_file = {})
50
+ @attributes = {}
51
+ @includes = []
52
+ @include_attributes = {}
53
+ merge!(extract_hash(xcconfig_hash_or_file))
54
+ @includes_paths = @includes.map { |i| File.expand_path(i, @filepath.dirname) }
55
+ end
56
+
57
+ def inspect
58
+ to_hash.inspect
59
+ end
60
+
61
+ def ==(other)
62
+ other.attributes == attributes && other.includes_paths = @includes_paths
63
+ end
64
+
65
+ # @!group Serialization
66
+ #-------------------------------------------------------------------------#
67
+
68
+ # Sorts the internal data by setting name and serializes it in the xcconfig
69
+ # format.
70
+ #
71
+ # @example
72
+ #
73
+ # config = Config.new('PODS_ROOT' => '"$(SRCROOT)/Pods"', 'OTHER_LDFLAGS' => '-lxml2')
74
+ # config.to_s # => "OTHER_LDFLAGS = -lxml2\nPODS_ROOT = \"$(SRCROOT)/Pods\""
75
+ #
76
+ # @return [String] The serialized internal data.
77
+ #
78
+ def to_s(prefix = nil)
79
+ include_lines = @includes.map { |path| "#include \"#{normalized_xcconfig_path(path)}\"" }
80
+ settings = to_hash(prefix).sort_by(&:first).map { |k, v| "#{k} = #{v}".strip }
81
+ (include_lines + settings).join("\n") << "\n"
82
+ end
83
+
84
+ # Writes the serialized representation of the internal data to the given
85
+ # path.
86
+ #
87
+ # @param [Pathname] pathname
88
+ # The file where the data should be written to.
89
+ #
90
+ # @return [void]
91
+ #
92
+ def save_as(pathname, prefix = nil)
93
+ return if File.exist?(pathname) && (XCConfig.new(pathname) == self)
94
+
95
+ pathname.open('w') { |file| file << to_s(prefix) }
96
+ end
97
+
98
+ # The hash representation of the xcconfig. The hash includes the
99
+ # frameworks, the weak frameworks, the libraries and the simple other
100
+ # linker flags in the `Other Linker Flags` (`OTHER_LDFLAGS`).
101
+ #
102
+ # @note All the values are sorted to have a consistent output in Ruby
103
+ # 1.8.7.
104
+ #
105
+ # @return [Hash] The hash representation
106
+ #
107
+ def to_hash(prefix = nil)
108
+ result = attributes.dup
109
+ result.reject! { |_, v| INHERITED.any? { |i| i == v.to_s.strip } }
110
+ if prefix
111
+ Hash[result.map { |k, v| [prefix + k, v] }]
112
+ else
113
+ result
114
+ end
115
+ end
116
+
117
+ alias to_h to_hash
118
+
119
+ # @!group Merging
120
+ #-------------------------------------------------------------------------#
121
+
122
+ # Merges the given xcconfig representation in the receiver.
123
+ # @note If a key in the given hash already exists in the internal data
124
+ # then its value is appended.
125
+ #
126
+ # @param [Hash, Config] config
127
+ # The xcconfig representation to merge.
128
+ #
129
+ # @todo The logic to normalize an hash should be extracted and the
130
+ # initializer should not call this method.
131
+ #
132
+ # @return [void]
133
+ #
134
+ def merge!(xcconfig)
135
+ if xcconfig.is_a? XCConfig
136
+ merge_attributes!(xcconfig.attributes)
137
+ else
138
+ merge_attributes!(xcconfig.to_hash)
139
+ end
140
+ end
141
+ alias << merge!
142
+
143
+ # Creates a new #{Config} with the data of the receiver merged with the
144
+ # given xcconfig representation.
145
+ #
146
+ # @param [Hash, Config] config
147
+ # The xcconfig representation to merge.
148
+ #
149
+ # @return [Config] the new xcconfig.
150
+ #
151
+ def merge(config)
152
+ dup.tap { |x| x.merge!(config) }
153
+ end
154
+
155
+ # @return [Config] A copy of the receiver.
156
+ #
157
+ def dup
158
+ HMap::XCConfig.new(to_hash.dup)
159
+ end
160
+
161
+ #-------------------------------------------------------------------------#
162
+
163
+ private
164
+
165
+ # @!group Private Helpers
166
+
167
+ # Returns a hash from the given argument reading it from disk if necessary.
168
+ #
169
+ # @param [String, Pathname, Hash] argument
170
+ # The source from where the hash should be extracted.
171
+ #
172
+ # @return [Hash]
173
+ #
174
+ def extract_hash(argument)
175
+ return argument if argument.is_a?(Hash)
176
+
177
+ if argument.respond_to? :read
178
+ @filepath = Pathname.new(argument.to_path)
179
+ hash_from_file_content(argument.read)
180
+ elsif File.readable?(argument.to_s)
181
+ @filepath = Pathname.new(argument.to_s)
182
+ hash_from_file_content(File.read(argument))
183
+ else
184
+ argument
185
+ end
186
+ end
187
+
188
+ # Returns a hash from the string representation of an Xcconfig file.
189
+ #
190
+ # @param [String] string
191
+ # The string representation of an xcconfig file.
192
+ #
193
+ # @return [Hash] the hash containing the xcconfig data.
194
+ #
195
+ def hash_from_file_content(string)
196
+ hash = {}
197
+ string.split("\n").each do |line|
198
+ uncommented_line = strip_comment(line)
199
+ e = extract_include(uncommented_line)
200
+ if e.nil?
201
+ key, value = extract_key_value(uncommented_line)
202
+ next unless key
203
+ value.gsub!(INHERITED_REGEXP) { |m| hash.fetch(key, m) }
204
+ hash[key] = value
205
+ else
206
+ @includes.push normalized_xcconfig_path(e)
207
+ end
208
+ end
209
+ hash
210
+ end
211
+
212
+ # Merges the given attributes hash while ensuring values are not duplicated.
213
+ #
214
+ # @param [Hash] attributes
215
+ # The attributes hash to merge into @attributes.
216
+ #
217
+ # @return [void]
218
+ #
219
+ def merge_attributes!(attributes)
220
+ @attributes.merge!(attributes) do |_, v1, v2|
221
+ v1 = v1.strip
222
+ v2 = v2.strip
223
+ v1_split = v1.shellsplit
224
+ v2_split = v2.shellsplit
225
+ if (v2_split - v1_split).empty? || v1_split.first(v2_split.size) == v2_split
226
+ v1
227
+ elsif v2_split.first(v1_split.size) == v1_split
228
+ v2
229
+ else
230
+ "#{v1} #{v2}"
231
+ end
232
+ end
233
+ end
234
+
235
+ # Strips the comments from a line of an xcconfig string.
236
+ #
237
+ # @param [String] line
238
+ # the line to process.
239
+ #
240
+ # @return [String] the uncommented line.
241
+ #
242
+ def strip_comment(line)
243
+ line.partition('//').first
244
+ end
245
+
246
+ # Returns the file included by a line of an xcconfig string if present.
247
+ #
248
+ # @param [String] line
249
+ # the line to process.
250
+ #
251
+ # @return [String] the included file.
252
+ # @return [Nil] if no include was found in the line.
253
+ #
254
+ def extract_include(line)
255
+ regexp = /#include\s*"(.+)"/
256
+ match = line.match(regexp)
257
+ match[1] if match
258
+ end
259
+
260
+ # Returns the key and the value described by the given line of an xcconfig.
261
+ #
262
+ # @param [String] line
263
+ # the line to process.
264
+ #
265
+ # @return [Array] A tuple where the first entry is the key and the second
266
+ # entry is the value.
267
+ #
268
+ def extract_key_value(line)
269
+ match = line.match(KEY_VALUE_PATTERN)
270
+ if match
271
+ key = match[1]
272
+ value = match[2]
273
+ [key.strip, value.strip]
274
+ else
275
+ []
276
+ end
277
+ end
278
+
279
+ # Normalizes the given path to an xcconfing file to be used in includes,
280
+ # appending the extension if necessary.
281
+ #
282
+ # @param [String] path
283
+ # The path of the file which will be included in the xcconfig.
284
+ #
285
+ # @return [String] The normalized path.
286
+ #
287
+ def normalized_xcconfig_path(path)
288
+ if File.extname(path) == '.xcconfig'
289
+ path
290
+ else
291
+ "#{path}.xcconfig"
292
+ end
293
+ end
294
+ end
295
+ end