Initial commit
[yaffs-website] / node_modules / node-gyp / gyp / pylib / gyp / xcode_emulation.py
1 # Copyright (c) 2012 Google Inc. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
4
5 """
6 This module contains classes that help to emulate xcodebuild behavior on top of
7 other build systems, such as make and ninja.
8 """
9
10 import copy
11 import gyp.common
12 import os
13 import os.path
14 import re
15 import shlex
16 import subprocess
17 import sys
18 import tempfile
19 from gyp.common import GypError
20
21 # Populated lazily by XcodeVersion, for efficiency, and to fix an issue when
22 # "xcodebuild" is called too quickly (it has been found to return incorrect
23 # version number).
24 XCODE_VERSION_CACHE = None
25
26 # Populated lazily by GetXcodeArchsDefault, to an |XcodeArchsDefault| instance
27 # corresponding to the installed version of Xcode.
28 XCODE_ARCHS_DEFAULT_CACHE = None
29
30
31 def XcodeArchsVariableMapping(archs, archs_including_64_bit=None):
32   """Constructs a dictionary with expansion for $(ARCHS_STANDARD) variable,
33   and optionally for $(ARCHS_STANDARD_INCLUDING_64_BIT)."""
34   mapping = {'$(ARCHS_STANDARD)': archs}
35   if archs_including_64_bit:
36     mapping['$(ARCHS_STANDARD_INCLUDING_64_BIT)'] = archs_including_64_bit
37   return mapping
38
39 class XcodeArchsDefault(object):
40   """A class to resolve ARCHS variable from xcode_settings, resolving Xcode
41   macros and implementing filtering by VALID_ARCHS. The expansion of macros
42   depends on the SDKROOT used ("macosx", "iphoneos", "iphonesimulator") and
43   on the version of Xcode.
44   """
45
46   # Match variable like $(ARCHS_STANDARD).
47   variable_pattern = re.compile(r'\$\([a-zA-Z_][a-zA-Z0-9_]*\)$')
48
49   def __init__(self, default, mac, iphonesimulator, iphoneos):
50     self._default = (default,)
51     self._archs = {'mac': mac, 'ios': iphoneos, 'iossim': iphonesimulator}
52
53   def _VariableMapping(self, sdkroot):
54     """Returns the dictionary of variable mapping depending on the SDKROOT."""
55     sdkroot = sdkroot.lower()
56     if 'iphoneos' in sdkroot:
57       return self._archs['ios']
58     elif 'iphonesimulator' in sdkroot:
59       return self._archs['iossim']
60     else:
61       return self._archs['mac']
62
63   def _ExpandArchs(self, archs, sdkroot):
64     """Expands variables references in ARCHS, and remove duplicates."""
65     variable_mapping = self._VariableMapping(sdkroot)
66     expanded_archs = []
67     for arch in archs:
68       if self.variable_pattern.match(arch):
69         variable = arch
70         try:
71           variable_expansion = variable_mapping[variable]
72           for arch in variable_expansion:
73             if arch not in expanded_archs:
74               expanded_archs.append(arch)
75         except KeyError as e:
76           print 'Warning: Ignoring unsupported variable "%s".' % variable
77       elif arch not in expanded_archs:
78         expanded_archs.append(arch)
79     return expanded_archs
80
81   def ActiveArchs(self, archs, valid_archs, sdkroot):
82     """Expands variables references in ARCHS, and filter by VALID_ARCHS if it
83     is defined (if not set, Xcode accept any value in ARCHS, otherwise, only
84     values present in VALID_ARCHS are kept)."""
85     expanded_archs = self._ExpandArchs(archs or self._default, sdkroot or '')
86     if valid_archs:
87       filtered_archs = []
88       for arch in expanded_archs:
89         if arch in valid_archs:
90           filtered_archs.append(arch)
91       expanded_archs = filtered_archs
92     return expanded_archs
93
94
95 def GetXcodeArchsDefault():
96   """Returns the |XcodeArchsDefault| object to use to expand ARCHS for the
97   installed version of Xcode. The default values used by Xcode for ARCHS
98   and the expansion of the variables depends on the version of Xcode used.
99
100   For all version anterior to Xcode 5.0 or posterior to Xcode 5.1 included
101   uses $(ARCHS_STANDARD) if ARCHS is unset, while Xcode 5.0 to 5.0.2 uses
102   $(ARCHS_STANDARD_INCLUDING_64_BIT). This variable was added to Xcode 5.0
103   and deprecated with Xcode 5.1.
104
105   For "macosx" SDKROOT, all version starting with Xcode 5.0 includes 64-bit
106   architecture as part of $(ARCHS_STANDARD) and default to only building it.
107
108   For "iphoneos" and "iphonesimulator" SDKROOT, 64-bit architectures are part
109   of $(ARCHS_STANDARD_INCLUDING_64_BIT) from Xcode 5.0. From Xcode 5.1, they
110   are also part of $(ARCHS_STANDARD).
111
112   All thoses rules are coded in the construction of the |XcodeArchsDefault|
113   object to use depending on the version of Xcode detected. The object is
114   for performance reason."""
115   global XCODE_ARCHS_DEFAULT_CACHE
116   if XCODE_ARCHS_DEFAULT_CACHE:
117     return XCODE_ARCHS_DEFAULT_CACHE
118   xcode_version, _ = XcodeVersion()
119   if xcode_version < '0500':
120     XCODE_ARCHS_DEFAULT_CACHE = XcodeArchsDefault(
121         '$(ARCHS_STANDARD)',
122         XcodeArchsVariableMapping(['i386']),
123         XcodeArchsVariableMapping(['i386']),
124         XcodeArchsVariableMapping(['armv7']))
125   elif xcode_version < '0510':
126     XCODE_ARCHS_DEFAULT_CACHE = XcodeArchsDefault(
127         '$(ARCHS_STANDARD_INCLUDING_64_BIT)',
128         XcodeArchsVariableMapping(['x86_64'], ['x86_64']),
129         XcodeArchsVariableMapping(['i386'], ['i386', 'x86_64']),
130         XcodeArchsVariableMapping(
131             ['armv7', 'armv7s'],
132             ['armv7', 'armv7s', 'arm64']))
133   else:
134     XCODE_ARCHS_DEFAULT_CACHE = XcodeArchsDefault(
135         '$(ARCHS_STANDARD)',
136         XcodeArchsVariableMapping(['x86_64'], ['x86_64']),
137         XcodeArchsVariableMapping(['i386', 'x86_64'], ['i386', 'x86_64']),
138         XcodeArchsVariableMapping(
139             ['armv7', 'armv7s', 'arm64'],
140             ['armv7', 'armv7s', 'arm64']))
141   return XCODE_ARCHS_DEFAULT_CACHE
142
143
144 class XcodeSettings(object):
145   """A class that understands the gyp 'xcode_settings' object."""
146
147   # Populated lazily by _SdkPath(). Shared by all XcodeSettings, so cached
148   # at class-level for efficiency.
149   _sdk_path_cache = {}
150   _sdk_root_cache = {}
151
152   # Populated lazily by GetExtraPlistItems(). Shared by all XcodeSettings, so
153   # cached at class-level for efficiency.
154   _plist_cache = {}
155
156   # Populated lazily by GetIOSPostbuilds.  Shared by all XcodeSettings, so
157   # cached at class-level for efficiency.
158   _codesigning_key_cache = {}
159
160   def __init__(self, spec):
161     self.spec = spec
162
163     self.isIOS = False
164
165     # Per-target 'xcode_settings' are pushed down into configs earlier by gyp.
166     # This means self.xcode_settings[config] always contains all settings
167     # for that config -- the per-target settings as well. Settings that are
168     # the same for all configs are implicitly per-target settings.
169     self.xcode_settings = {}
170     configs = spec['configurations']
171     for configname, config in configs.iteritems():
172       self.xcode_settings[configname] = config.get('xcode_settings', {})
173       self._ConvertConditionalKeys(configname)
174       if self.xcode_settings[configname].get('IPHONEOS_DEPLOYMENT_TARGET',
175                                              None):
176         self.isIOS = True
177
178     # This is only non-None temporarily during the execution of some methods.
179     self.configname = None
180
181     # Used by _AdjustLibrary to match .a and .dylib entries in libraries.
182     self.library_re = re.compile(r'^lib([^/]+)\.(a|dylib)$')
183
184   def _ConvertConditionalKeys(self, configname):
185     """Converts or warns on conditional keys.  Xcode supports conditional keys,
186     such as CODE_SIGN_IDENTITY[sdk=iphoneos*].  This is a partial implementation
187     with some keys converted while the rest force a warning."""
188     settings = self.xcode_settings[configname]
189     conditional_keys = [key for key in settings if key.endswith(']')]
190     for key in conditional_keys:
191       # If you need more, speak up at http://crbug.com/122592
192       if key.endswith("[sdk=iphoneos*]"):
193         if configname.endswith("iphoneos"):
194           new_key = key.split("[")[0]
195           settings[new_key] = settings[key]
196       else:
197         print 'Warning: Conditional keys not implemented, ignoring:', \
198               ' '.join(conditional_keys)
199       del settings[key]
200
201   def _Settings(self):
202     assert self.configname
203     return self.xcode_settings[self.configname]
204
205   def _Test(self, test_key, cond_key, default):
206     return self._Settings().get(test_key, default) == cond_key
207
208   def _Appendf(self, lst, test_key, format_str, default=None):
209     if test_key in self._Settings():
210       lst.append(format_str % str(self._Settings()[test_key]))
211     elif default:
212       lst.append(format_str % str(default))
213
214   def _WarnUnimplemented(self, test_key):
215     if test_key in self._Settings():
216       print 'Warning: Ignoring not yet implemented key "%s".' % test_key
217
218   def IsBinaryOutputFormat(self, configname):
219     default = "binary" if self.isIOS else "xml"
220     format = self.xcode_settings[configname].get('INFOPLIST_OUTPUT_FORMAT',
221                                                  default)
222     return format == "binary"
223
224   def _IsBundle(self):
225     return int(self.spec.get('mac_bundle', 0)) != 0
226
227   def _IsIosAppExtension(self):
228     return int(self.spec.get('ios_app_extension', 0)) != 0
229
230   def _IsIosWatchKitExtension(self):
231     return int(self.spec.get('ios_watchkit_extension', 0)) != 0
232
233   def _IsIosWatchApp(self):
234     return int(self.spec.get('ios_watch_app', 0)) != 0
235
236   def GetFrameworkVersion(self):
237     """Returns the framework version of the current target. Only valid for
238     bundles."""
239     assert self._IsBundle()
240     return self.GetPerTargetSetting('FRAMEWORK_VERSION', default='A')
241
242   def GetWrapperExtension(self):
243     """Returns the bundle extension (.app, .framework, .plugin, etc).  Only
244     valid for bundles."""
245     assert self._IsBundle()
246     if self.spec['type'] in ('loadable_module', 'shared_library'):
247       default_wrapper_extension = {
248         'loadable_module': 'bundle',
249         'shared_library': 'framework',
250       }[self.spec['type']]
251       wrapper_extension = self.GetPerTargetSetting(
252           'WRAPPER_EXTENSION', default=default_wrapper_extension)
253       return '.' + self.spec.get('product_extension', wrapper_extension)
254     elif self.spec['type'] == 'executable':
255       if self._IsIosAppExtension() or self._IsIosWatchKitExtension():
256         return '.' + self.spec.get('product_extension', 'appex')
257       else:
258         return '.' + self.spec.get('product_extension', 'app')
259     else:
260       assert False, "Don't know extension for '%s', target '%s'" % (
261           self.spec['type'], self.spec['target_name'])
262
263   def GetProductName(self):
264     """Returns PRODUCT_NAME."""
265     return self.spec.get('product_name', self.spec['target_name'])
266
267   def GetFullProductName(self):
268     """Returns FULL_PRODUCT_NAME."""
269     if self._IsBundle():
270       return self.GetWrapperName()
271     else:
272       return self._GetStandaloneBinaryPath()
273
274   def GetWrapperName(self):
275     """Returns the directory name of the bundle represented by this target.
276     Only valid for bundles."""
277     assert self._IsBundle()
278     return self.GetProductName() + self.GetWrapperExtension()
279
280   def GetBundleContentsFolderPath(self):
281     """Returns the qualified path to the bundle's contents folder. E.g.
282     Chromium.app/Contents or Foo.bundle/Versions/A. Only valid for bundles."""
283     if self.isIOS:
284       return self.GetWrapperName()
285     assert self._IsBundle()
286     if self.spec['type'] == 'shared_library':
287       return os.path.join(
288           self.GetWrapperName(), 'Versions', self.GetFrameworkVersion())
289     else:
290       # loadable_modules have a 'Contents' folder like executables.
291       return os.path.join(self.GetWrapperName(), 'Contents')
292
293   def GetBundleResourceFolder(self):
294     """Returns the qualified path to the bundle's resource folder. E.g.
295     Chromium.app/Contents/Resources. Only valid for bundles."""
296     assert self._IsBundle()
297     if self.isIOS:
298       return self.GetBundleContentsFolderPath()
299     return os.path.join(self.GetBundleContentsFolderPath(), 'Resources')
300
301   def GetBundlePlistPath(self):
302     """Returns the qualified path to the bundle's plist file. E.g.
303     Chromium.app/Contents/Info.plist. Only valid for bundles."""
304     assert self._IsBundle()
305     if self.spec['type'] in ('executable', 'loadable_module'):
306       return os.path.join(self.GetBundleContentsFolderPath(), 'Info.plist')
307     else:
308       return os.path.join(self.GetBundleContentsFolderPath(),
309                           'Resources', 'Info.plist')
310
311   def GetProductType(self):
312     """Returns the PRODUCT_TYPE of this target."""
313     if self._IsIosAppExtension():
314       assert self._IsBundle(), ('ios_app_extension flag requires mac_bundle '
315           '(target %s)' % self.spec['target_name'])
316       return 'com.apple.product-type.app-extension'
317     if self._IsIosWatchKitExtension():
318       assert self._IsBundle(), ('ios_watchkit_extension flag requires '
319           'mac_bundle (target %s)' % self.spec['target_name'])
320       return 'com.apple.product-type.watchkit-extension'
321     if self._IsIosWatchApp():
322       assert self._IsBundle(), ('ios_watch_app flag requires mac_bundle '
323           '(target %s)' % self.spec['target_name'])
324       return 'com.apple.product-type.application.watchapp'
325     if self._IsBundle():
326       return {
327         'executable': 'com.apple.product-type.application',
328         'loadable_module': 'com.apple.product-type.bundle',
329         'shared_library': 'com.apple.product-type.framework',
330       }[self.spec['type']]
331     else:
332       return {
333         'executable': 'com.apple.product-type.tool',
334         'loadable_module': 'com.apple.product-type.library.dynamic',
335         'shared_library': 'com.apple.product-type.library.dynamic',
336         'static_library': 'com.apple.product-type.library.static',
337       }[self.spec['type']]
338
339   def GetMachOType(self):
340     """Returns the MACH_O_TYPE of this target."""
341     # Weird, but matches Xcode.
342     if not self._IsBundle() and self.spec['type'] == 'executable':
343       return ''
344     return {
345       'executable': 'mh_execute',
346       'static_library': 'staticlib',
347       'shared_library': 'mh_dylib',
348       'loadable_module': 'mh_bundle',
349     }[self.spec['type']]
350
351   def _GetBundleBinaryPath(self):
352     """Returns the name of the bundle binary of by this target.
353     E.g. Chromium.app/Contents/MacOS/Chromium. Only valid for bundles."""
354     assert self._IsBundle()
355     if self.spec['type'] in ('shared_library') or self.isIOS:
356       path = self.GetBundleContentsFolderPath()
357     elif self.spec['type'] in ('executable', 'loadable_module'):
358       path = os.path.join(self.GetBundleContentsFolderPath(), 'MacOS')
359     return os.path.join(path, self.GetExecutableName())
360
361   def _GetStandaloneExecutableSuffix(self):
362     if 'product_extension' in self.spec:
363       return '.' + self.spec['product_extension']
364     return {
365       'executable': '',
366       'static_library': '.a',
367       'shared_library': '.dylib',
368       'loadable_module': '.so',
369     }[self.spec['type']]
370
371   def _GetStandaloneExecutablePrefix(self):
372     return self.spec.get('product_prefix', {
373       'executable': '',
374       'static_library': 'lib',
375       'shared_library': 'lib',
376       # Non-bundled loadable_modules are called foo.so for some reason
377       # (that is, .so and no prefix) with the xcode build -- match that.
378       'loadable_module': '',
379     }[self.spec['type']])
380
381   def _GetStandaloneBinaryPath(self):
382     """Returns the name of the non-bundle binary represented by this target.
383     E.g. hello_world. Only valid for non-bundles."""
384     assert not self._IsBundle()
385     assert self.spec['type'] in (
386         'executable', 'shared_library', 'static_library', 'loadable_module'), (
387         'Unexpected type %s' % self.spec['type'])
388     target = self.spec['target_name']
389     if self.spec['type'] == 'static_library':
390       if target[:3] == 'lib':
391         target = target[3:]
392     elif self.spec['type'] in ('loadable_module', 'shared_library'):
393       if target[:3] == 'lib':
394         target = target[3:]
395
396     target_prefix = self._GetStandaloneExecutablePrefix()
397     target = self.spec.get('product_name', target)
398     target_ext = self._GetStandaloneExecutableSuffix()
399     return target_prefix + target + target_ext
400
401   def GetExecutableName(self):
402     """Returns the executable name of the bundle represented by this target.
403     E.g. Chromium."""
404     if self._IsBundle():
405       return self.spec.get('product_name', self.spec['target_name'])
406     else:
407       return self._GetStandaloneBinaryPath()
408
409   def GetExecutablePath(self):
410     """Returns the directory name of the bundle represented by this target. E.g.
411     Chromium.app/Contents/MacOS/Chromium."""
412     if self._IsBundle():
413       return self._GetBundleBinaryPath()
414     else:
415       return self._GetStandaloneBinaryPath()
416
417   def GetActiveArchs(self, configname):
418     """Returns the architectures this target should be built for."""
419     config_settings = self.xcode_settings[configname]
420     xcode_archs_default = GetXcodeArchsDefault()
421     return xcode_archs_default.ActiveArchs(
422         config_settings.get('ARCHS'),
423         config_settings.get('VALID_ARCHS'),
424         config_settings.get('SDKROOT'))
425
426   def _GetSdkVersionInfoItem(self, sdk, infoitem):
427     # xcodebuild requires Xcode and can't run on Command Line Tools-only
428     # systems from 10.7 onward.
429     # Since the CLT has no SDK paths anyway, returning None is the
430     # most sensible route and should still do the right thing.
431     try:
432       return GetStdout(['xcodebuild', '-version', '-sdk', sdk, infoitem])
433     except:
434       pass
435
436   def _SdkRoot(self, configname):
437     if configname is None:
438       configname = self.configname
439     return self.GetPerConfigSetting('SDKROOT', configname, default='')
440
441   def _SdkPath(self, configname=None):
442     sdk_root = self._SdkRoot(configname)
443     if sdk_root.startswith('/'):
444       return sdk_root
445     return self._XcodeSdkPath(sdk_root)
446
447   def _XcodeSdkPath(self, sdk_root):
448     if sdk_root not in XcodeSettings._sdk_path_cache:
449       sdk_path = self._GetSdkVersionInfoItem(sdk_root, 'Path')
450       XcodeSettings._sdk_path_cache[sdk_root] = sdk_path
451       if sdk_root:
452         XcodeSettings._sdk_root_cache[sdk_path] = sdk_root
453     return XcodeSettings._sdk_path_cache[sdk_root]
454
455   def _AppendPlatformVersionMinFlags(self, lst):
456     self._Appendf(lst, 'MACOSX_DEPLOYMENT_TARGET', '-mmacosx-version-min=%s')
457     if 'IPHONEOS_DEPLOYMENT_TARGET' in self._Settings():
458       # TODO: Implement this better?
459       sdk_path_basename = os.path.basename(self._SdkPath())
460       if sdk_path_basename.lower().startswith('iphonesimulator'):
461         self._Appendf(lst, 'IPHONEOS_DEPLOYMENT_TARGET',
462                       '-mios-simulator-version-min=%s')
463       else:
464         self._Appendf(lst, 'IPHONEOS_DEPLOYMENT_TARGET',
465                       '-miphoneos-version-min=%s')
466
467   def GetCflags(self, configname, arch=None):
468     """Returns flags that need to be added to .c, .cc, .m, and .mm
469     compilations."""
470     # This functions (and the similar ones below) do not offer complete
471     # emulation of all xcode_settings keys. They're implemented on demand.
472
473     self.configname = configname
474     cflags = []
475
476     sdk_root = self._SdkPath()
477     if 'SDKROOT' in self._Settings() and sdk_root:
478       cflags.append('-isysroot %s' % sdk_root)
479
480     if self._Test('CLANG_WARN_CONSTANT_CONVERSION', 'YES', default='NO'):
481       cflags.append('-Wconstant-conversion')
482
483     if self._Test('GCC_CHAR_IS_UNSIGNED_CHAR', 'YES', default='NO'):
484       cflags.append('-funsigned-char')
485
486     if self._Test('GCC_CW_ASM_SYNTAX', 'YES', default='YES'):
487       cflags.append('-fasm-blocks')
488
489     if 'GCC_DYNAMIC_NO_PIC' in self._Settings():
490       if self._Settings()['GCC_DYNAMIC_NO_PIC'] == 'YES':
491         cflags.append('-mdynamic-no-pic')
492     else:
493       pass
494       # TODO: In this case, it depends on the target. xcode passes
495       # mdynamic-no-pic by default for executable and possibly static lib
496       # according to mento
497
498     if self._Test('GCC_ENABLE_PASCAL_STRINGS', 'YES', default='YES'):
499       cflags.append('-mpascal-strings')
500
501     self._Appendf(cflags, 'GCC_OPTIMIZATION_LEVEL', '-O%s', default='s')
502
503     if self._Test('GCC_GENERATE_DEBUGGING_SYMBOLS', 'YES', default='YES'):
504       dbg_format = self._Settings().get('DEBUG_INFORMATION_FORMAT', 'dwarf')
505       if dbg_format == 'dwarf':
506         cflags.append('-gdwarf-2')
507       elif dbg_format == 'stabs':
508         raise NotImplementedError('stabs debug format is not supported yet.')
509       elif dbg_format == 'dwarf-with-dsym':
510         cflags.append('-gdwarf-2')
511       else:
512         raise NotImplementedError('Unknown debug format %s' % dbg_format)
513
514     if self._Settings().get('GCC_STRICT_ALIASING') == 'YES':
515       cflags.append('-fstrict-aliasing')
516     elif self._Settings().get('GCC_STRICT_ALIASING') == 'NO':
517       cflags.append('-fno-strict-aliasing')
518
519     if self._Test('GCC_SYMBOLS_PRIVATE_EXTERN', 'YES', default='NO'):
520       cflags.append('-fvisibility=hidden')
521
522     if self._Test('GCC_TREAT_WARNINGS_AS_ERRORS', 'YES', default='NO'):
523       cflags.append('-Werror')
524
525     if self._Test('GCC_WARN_ABOUT_MISSING_NEWLINE', 'YES', default='NO'):
526       cflags.append('-Wnewline-eof')
527
528     # In Xcode, this is only activated when GCC_COMPILER_VERSION is clang or
529     # llvm-gcc. It also requires a fairly recent libtool, and
530     # if the system clang isn't used, DYLD_LIBRARY_PATH needs to contain the
531     # path to the libLTO.dylib that matches the used clang.
532     if self._Test('LLVM_LTO', 'YES', default='NO'):
533       cflags.append('-flto')
534
535     self._AppendPlatformVersionMinFlags(cflags)
536
537     # TODO:
538     if self._Test('COPY_PHASE_STRIP', 'YES', default='NO'):
539       self._WarnUnimplemented('COPY_PHASE_STRIP')
540     self._WarnUnimplemented('GCC_DEBUGGING_SYMBOLS')
541     self._WarnUnimplemented('GCC_ENABLE_OBJC_EXCEPTIONS')
542
543     # TODO: This is exported correctly, but assigning to it is not supported.
544     self._WarnUnimplemented('MACH_O_TYPE')
545     self._WarnUnimplemented('PRODUCT_TYPE')
546
547     if arch is not None:
548       archs = [arch]
549     else:
550       assert self.configname
551       archs = self.GetActiveArchs(self.configname)
552     if len(archs) != 1:
553       # TODO: Supporting fat binaries will be annoying.
554       self._WarnUnimplemented('ARCHS')
555       archs = ['i386']
556     cflags.append('-arch ' + archs[0])
557
558     if archs[0] in ('i386', 'x86_64'):
559       if self._Test('GCC_ENABLE_SSE3_EXTENSIONS', 'YES', default='NO'):
560         cflags.append('-msse3')
561       if self._Test('GCC_ENABLE_SUPPLEMENTAL_SSE3_INSTRUCTIONS', 'YES',
562                     default='NO'):
563         cflags.append('-mssse3')  # Note 3rd 's'.
564       if self._Test('GCC_ENABLE_SSE41_EXTENSIONS', 'YES', default='NO'):
565         cflags.append('-msse4.1')
566       if self._Test('GCC_ENABLE_SSE42_EXTENSIONS', 'YES', default='NO'):
567         cflags.append('-msse4.2')
568
569     cflags += self._Settings().get('WARNING_CFLAGS', [])
570
571     if sdk_root:
572       framework_root = sdk_root
573     else:
574       framework_root = ''
575     config = self.spec['configurations'][self.configname]
576     framework_dirs = config.get('mac_framework_dirs', [])
577     for directory in framework_dirs:
578       cflags.append('-F' + directory.replace('$(SDKROOT)', framework_root))
579
580     self.configname = None
581     return cflags
582
583   def GetCflagsC(self, configname):
584     """Returns flags that need to be added to .c, and .m compilations."""
585     self.configname = configname
586     cflags_c = []
587     if self._Settings().get('GCC_C_LANGUAGE_STANDARD', '') == 'ansi':
588       cflags_c.append('-ansi')
589     else:
590       self._Appendf(cflags_c, 'GCC_C_LANGUAGE_STANDARD', '-std=%s')
591     cflags_c += self._Settings().get('OTHER_CFLAGS', [])
592     self.configname = None
593     return cflags_c
594
595   def GetCflagsCC(self, configname):
596     """Returns flags that need to be added to .cc, and .mm compilations."""
597     self.configname = configname
598     cflags_cc = []
599
600     clang_cxx_language_standard = self._Settings().get(
601         'CLANG_CXX_LANGUAGE_STANDARD')
602     # Note: Don't make c++0x to c++11 so that c++0x can be used with older
603     # clangs that don't understand c++11 yet (like Xcode 4.2's).
604     if clang_cxx_language_standard:
605       cflags_cc.append('-std=%s' % clang_cxx_language_standard)
606
607     self._Appendf(cflags_cc, 'CLANG_CXX_LIBRARY', '-stdlib=%s')
608
609     if self._Test('GCC_ENABLE_CPP_RTTI', 'NO', default='YES'):
610       cflags_cc.append('-fno-rtti')
611     if self._Test('GCC_ENABLE_CPP_EXCEPTIONS', 'NO', default='YES'):
612       cflags_cc.append('-fno-exceptions')
613     if self._Test('GCC_INLINES_ARE_PRIVATE_EXTERN', 'YES', default='NO'):
614       cflags_cc.append('-fvisibility-inlines-hidden')
615     if self._Test('GCC_THREADSAFE_STATICS', 'NO', default='YES'):
616       cflags_cc.append('-fno-threadsafe-statics')
617     # Note: This flag is a no-op for clang, it only has an effect for gcc.
618     if self._Test('GCC_WARN_ABOUT_INVALID_OFFSETOF_MACRO', 'NO', default='YES'):
619       cflags_cc.append('-Wno-invalid-offsetof')
620
621     other_ccflags = []
622
623     for flag in self._Settings().get('OTHER_CPLUSPLUSFLAGS', ['$(inherited)']):
624       # TODO: More general variable expansion. Missing in many other places too.
625       if flag in ('$inherited', '$(inherited)', '${inherited}'):
626         flag = '$OTHER_CFLAGS'
627       if flag in ('$OTHER_CFLAGS', '$(OTHER_CFLAGS)', '${OTHER_CFLAGS}'):
628         other_ccflags += self._Settings().get('OTHER_CFLAGS', [])
629       else:
630         other_ccflags.append(flag)
631     cflags_cc += other_ccflags
632
633     self.configname = None
634     return cflags_cc
635
636   def _AddObjectiveCGarbageCollectionFlags(self, flags):
637     gc_policy = self._Settings().get('GCC_ENABLE_OBJC_GC', 'unsupported')
638     if gc_policy == 'supported':
639       flags.append('-fobjc-gc')
640     elif gc_policy == 'required':
641       flags.append('-fobjc-gc-only')
642
643   def _AddObjectiveCARCFlags(self, flags):
644     if self._Test('CLANG_ENABLE_OBJC_ARC', 'YES', default='NO'):
645       flags.append('-fobjc-arc')
646
647   def _AddObjectiveCMissingPropertySynthesisFlags(self, flags):
648     if self._Test('CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS',
649                   'YES', default='NO'):
650       flags.append('-Wobjc-missing-property-synthesis')
651
652   def GetCflagsObjC(self, configname):
653     """Returns flags that need to be added to .m compilations."""
654     self.configname = configname
655     cflags_objc = []
656     self._AddObjectiveCGarbageCollectionFlags(cflags_objc)
657     self._AddObjectiveCARCFlags(cflags_objc)
658     self._AddObjectiveCMissingPropertySynthesisFlags(cflags_objc)
659     self.configname = None
660     return cflags_objc
661
662   def GetCflagsObjCC(self, configname):
663     """Returns flags that need to be added to .mm compilations."""
664     self.configname = configname
665     cflags_objcc = []
666     self._AddObjectiveCGarbageCollectionFlags(cflags_objcc)
667     self._AddObjectiveCARCFlags(cflags_objcc)
668     self._AddObjectiveCMissingPropertySynthesisFlags(cflags_objcc)
669     if self._Test('GCC_OBJC_CALL_CXX_CDTORS', 'YES', default='NO'):
670       cflags_objcc.append('-fobjc-call-cxx-cdtors')
671     self.configname = None
672     return cflags_objcc
673
674   def GetInstallNameBase(self):
675     """Return DYLIB_INSTALL_NAME_BASE for this target."""
676     # Xcode sets this for shared_libraries, and for nonbundled loadable_modules.
677     if (self.spec['type'] != 'shared_library' and
678         (self.spec['type'] != 'loadable_module' or self._IsBundle())):
679       return None
680     install_base = self.GetPerTargetSetting(
681         'DYLIB_INSTALL_NAME_BASE',
682         default='/Library/Frameworks' if self._IsBundle() else '/usr/local/lib')
683     return install_base
684
685   def _StandardizePath(self, path):
686     """Do :standardizepath processing for path."""
687     # I'm not quite sure what :standardizepath does. Just call normpath(),
688     # but don't let @executable_path/../foo collapse to foo.
689     if '/' in path:
690       prefix, rest = '', path
691       if path.startswith('@'):
692         prefix, rest = path.split('/', 1)
693       rest = os.path.normpath(rest)  # :standardizepath
694       path = os.path.join(prefix, rest)
695     return path
696
697   def GetInstallName(self):
698     """Return LD_DYLIB_INSTALL_NAME for this target."""
699     # Xcode sets this for shared_libraries, and for nonbundled loadable_modules.
700     if (self.spec['type'] != 'shared_library' and
701         (self.spec['type'] != 'loadable_module' or self._IsBundle())):
702       return None
703
704     default_install_name = \
705         '$(DYLIB_INSTALL_NAME_BASE:standardizepath)/$(EXECUTABLE_PATH)'
706     install_name = self.GetPerTargetSetting(
707         'LD_DYLIB_INSTALL_NAME', default=default_install_name)
708
709     # Hardcode support for the variables used in chromium for now, to
710     # unblock people using the make build.
711     if '$' in install_name:
712       assert install_name in ('$(DYLIB_INSTALL_NAME_BASE:standardizepath)/'
713           '$(WRAPPER_NAME)/$(PRODUCT_NAME)', default_install_name), (
714           'Variables in LD_DYLIB_INSTALL_NAME are not generally supported '
715           'yet in target \'%s\' (got \'%s\')' %
716               (self.spec['target_name'], install_name))
717
718       install_name = install_name.replace(
719           '$(DYLIB_INSTALL_NAME_BASE:standardizepath)',
720           self._StandardizePath(self.GetInstallNameBase()))
721       if self._IsBundle():
722         # These are only valid for bundles, hence the |if|.
723         install_name = install_name.replace(
724             '$(WRAPPER_NAME)', self.GetWrapperName())
725         install_name = install_name.replace(
726             '$(PRODUCT_NAME)', self.GetProductName())
727       else:
728         assert '$(WRAPPER_NAME)' not in install_name
729         assert '$(PRODUCT_NAME)' not in install_name
730
731       install_name = install_name.replace(
732           '$(EXECUTABLE_PATH)', self.GetExecutablePath())
733     return install_name
734
735   def _MapLinkerFlagFilename(self, ldflag, gyp_to_build_path):
736     """Checks if ldflag contains a filename and if so remaps it from
737     gyp-directory-relative to build-directory-relative."""
738     # This list is expanded on demand.
739     # They get matched as:
740     #   -exported_symbols_list file
741     #   -Wl,exported_symbols_list file
742     #   -Wl,exported_symbols_list,file
743     LINKER_FILE = r'(\S+)'
744     WORD = r'\S+'
745     linker_flags = [
746       ['-exported_symbols_list', LINKER_FILE],    # Needed for NaCl.
747       ['-unexported_symbols_list', LINKER_FILE],
748       ['-reexported_symbols_list', LINKER_FILE],
749       ['-sectcreate', WORD, WORD, LINKER_FILE],   # Needed for remoting.
750     ]
751     for flag_pattern in linker_flags:
752       regex = re.compile('(?:-Wl,)?' + '[ ,]'.join(flag_pattern))
753       m = regex.match(ldflag)
754       if m:
755         ldflag = ldflag[:m.start(1)] + gyp_to_build_path(m.group(1)) + \
756                  ldflag[m.end(1):]
757     # Required for ffmpeg (no idea why they don't use LIBRARY_SEARCH_PATHS,
758     # TODO(thakis): Update ffmpeg.gyp):
759     if ldflag.startswith('-L'):
760       ldflag = '-L' + gyp_to_build_path(ldflag[len('-L'):])
761     return ldflag
762
763   def GetLdflags(self, configname, product_dir, gyp_to_build_path, arch=None):
764     """Returns flags that need to be passed to the linker.
765
766     Args:
767         configname: The name of the configuration to get ld flags for.
768         product_dir: The directory where products such static and dynamic
769             libraries are placed. This is added to the library search path.
770         gyp_to_build_path: A function that converts paths relative to the
771             current gyp file to paths relative to the build direcotry.
772     """
773     self.configname = configname
774     ldflags = []
775
776     # The xcode build is relative to a gyp file's directory, and OTHER_LDFLAGS
777     # can contain entries that depend on this. Explicitly absolutify these.
778     for ldflag in self._Settings().get('OTHER_LDFLAGS', []):
779       ldflags.append(self._MapLinkerFlagFilename(ldflag, gyp_to_build_path))
780
781     if self._Test('DEAD_CODE_STRIPPING', 'YES', default='NO'):
782       ldflags.append('-Wl,-dead_strip')
783
784     if self._Test('PREBINDING', 'YES', default='NO'):
785       ldflags.append('-Wl,-prebind')
786
787     self._Appendf(
788         ldflags, 'DYLIB_COMPATIBILITY_VERSION', '-compatibility_version %s')
789     self._Appendf(
790         ldflags, 'DYLIB_CURRENT_VERSION', '-current_version %s')
791
792     self._AppendPlatformVersionMinFlags(ldflags)
793
794     if 'SDKROOT' in self._Settings() and self._SdkPath():
795       ldflags.append('-isysroot ' + self._SdkPath())
796
797     for library_path in self._Settings().get('LIBRARY_SEARCH_PATHS', []):
798       ldflags.append('-L' + gyp_to_build_path(library_path))
799
800     if 'ORDER_FILE' in self._Settings():
801       ldflags.append('-Wl,-order_file ' +
802                      '-Wl,' + gyp_to_build_path(
803                                   self._Settings()['ORDER_FILE']))
804
805     if arch is not None:
806       archs = [arch]
807     else:
808       assert self.configname
809       archs = self.GetActiveArchs(self.configname)
810     if len(archs) != 1:
811       # TODO: Supporting fat binaries will be annoying.
812       self._WarnUnimplemented('ARCHS')
813       archs = ['i386']
814     ldflags.append('-arch ' + archs[0])
815
816     # Xcode adds the product directory by default.
817     ldflags.append('-L' + product_dir)
818
819     install_name = self.GetInstallName()
820     if install_name and self.spec['type'] != 'loadable_module':
821       ldflags.append('-install_name ' + install_name.replace(' ', r'\ '))
822
823     for rpath in self._Settings().get('LD_RUNPATH_SEARCH_PATHS', []):
824       ldflags.append('-Wl,-rpath,' + rpath)
825
826     sdk_root = self._SdkPath()
827     if not sdk_root:
828       sdk_root = ''
829     config = self.spec['configurations'][self.configname]
830     framework_dirs = config.get('mac_framework_dirs', [])
831     for directory in framework_dirs:
832       ldflags.append('-F' + directory.replace('$(SDKROOT)', sdk_root))
833
834     is_extension = self._IsIosAppExtension() or self._IsIosWatchKitExtension()
835     if sdk_root and is_extension:
836       # Adds the link flags for extensions. These flags are common for all
837       # extensions and provide loader and main function.
838       # These flags reflect the compilation options used by xcode to compile
839       # extensions.
840       ldflags.append('-lpkstart')
841       if XcodeVersion() < '0900':
842         ldflags.append(sdk_root +
843             '/System/Library/PrivateFrameworks/PlugInKit.framework/PlugInKit')
844       ldflags.append('-fapplication-extension')
845       ldflags.append('-Xlinker -rpath '
846           '-Xlinker @executable_path/../../Frameworks')
847
848     self._Appendf(ldflags, 'CLANG_CXX_LIBRARY', '-stdlib=%s')
849
850     self.configname = None
851     return ldflags
852
853   def GetLibtoolflags(self, configname):
854     """Returns flags that need to be passed to the static linker.
855
856     Args:
857         configname: The name of the configuration to get ld flags for.
858     """
859     self.configname = configname
860     libtoolflags = []
861
862     for libtoolflag in self._Settings().get('OTHER_LDFLAGS', []):
863       libtoolflags.append(libtoolflag)
864     # TODO(thakis): ARCHS?
865
866     self.configname = None
867     return libtoolflags
868
869   def GetPerTargetSettings(self):
870     """Gets a list of all the per-target settings. This will only fetch keys
871     whose values are the same across all configurations."""
872     first_pass = True
873     result = {}
874     for configname in sorted(self.xcode_settings.keys()):
875       if first_pass:
876         result = dict(self.xcode_settings[configname])
877         first_pass = False
878       else:
879         for key, value in self.xcode_settings[configname].iteritems():
880           if key not in result:
881             continue
882           elif result[key] != value:
883             del result[key]
884     return result
885
886   def GetPerConfigSetting(self, setting, configname, default=None):
887     if configname in self.xcode_settings:
888       return self.xcode_settings[configname].get(setting, default)
889     else:
890       return self.GetPerTargetSetting(setting, default)
891
892   def GetPerTargetSetting(self, setting, default=None):
893     """Tries to get xcode_settings.setting from spec. Assumes that the setting
894        has the same value in all configurations and throws otherwise."""
895     is_first_pass = True
896     result = None
897     for configname in sorted(self.xcode_settings.keys()):
898       if is_first_pass:
899         result = self.xcode_settings[configname].get(setting, None)
900         is_first_pass = False
901       else:
902         assert result == self.xcode_settings[configname].get(setting, None), (
903             "Expected per-target setting for '%s', got per-config setting "
904             "(target %s)" % (setting, self.spec['target_name']))
905     if result is None:
906       return default
907     return result
908
909   def _GetStripPostbuilds(self, configname, output_binary, quiet):
910     """Returns a list of shell commands that contain the shell commands
911     neccessary to strip this target's binary. These should be run as postbuilds
912     before the actual postbuilds run."""
913     self.configname = configname
914
915     result = []
916     if (self._Test('DEPLOYMENT_POSTPROCESSING', 'YES', default='NO') and
917         self._Test('STRIP_INSTALLED_PRODUCT', 'YES', default='NO')):
918
919       default_strip_style = 'debugging'
920       if self.spec['type'] == 'loadable_module' and self._IsBundle():
921         default_strip_style = 'non-global'
922       elif self.spec['type'] == 'executable':
923         default_strip_style = 'all'
924
925       strip_style = self._Settings().get('STRIP_STYLE', default_strip_style)
926       strip_flags = {
927         'all': '',
928         'non-global': '-x',
929         'debugging': '-S',
930       }[strip_style]
931
932       explicit_strip_flags = self._Settings().get('STRIPFLAGS', '')
933       if explicit_strip_flags:
934         strip_flags += ' ' + _NormalizeEnvVarReferences(explicit_strip_flags)
935
936       if not quiet:
937         result.append('echo STRIP\\(%s\\)' % self.spec['target_name'])
938       result.append('strip %s %s' % (strip_flags, output_binary))
939
940     self.configname = None
941     return result
942
943   def _GetDebugInfoPostbuilds(self, configname, output, output_binary, quiet):
944     """Returns a list of shell commands that contain the shell commands
945     neccessary to massage this target's debug information. These should be run
946     as postbuilds before the actual postbuilds run."""
947     self.configname = configname
948
949     # For static libraries, no dSYMs are created.
950     result = []
951     if (self._Test('GCC_GENERATE_DEBUGGING_SYMBOLS', 'YES', default='YES') and
952         self._Test(
953             'DEBUG_INFORMATION_FORMAT', 'dwarf-with-dsym', default='dwarf') and
954         self.spec['type'] != 'static_library'):
955       if not quiet:
956         result.append('echo DSYMUTIL\\(%s\\)' % self.spec['target_name'])
957       result.append('dsymutil %s -o %s' % (output_binary, output + '.dSYM'))
958
959     self.configname = None
960     return result
961
962   def _GetTargetPostbuilds(self, configname, output, output_binary,
963                            quiet=False):
964     """Returns a list of shell commands that contain the shell commands
965     to run as postbuilds for this target, before the actual postbuilds."""
966     # dSYMs need to build before stripping happens.
967     return (
968         self._GetDebugInfoPostbuilds(configname, output, output_binary, quiet) +
969         self._GetStripPostbuilds(configname, output_binary, quiet))
970
971   def _GetIOSPostbuilds(self, configname, output_binary):
972     """Return a shell command to codesign the iOS output binary so it can
973     be deployed to a device.  This should be run as the very last step of the
974     build."""
975     if not (self.isIOS and self.spec['type'] == 'executable'):
976       return []
977
978     settings = self.xcode_settings[configname]
979     key = self._GetIOSCodeSignIdentityKey(settings)
980     if not key:
981       return []
982
983     # Warn for any unimplemented signing xcode keys.
984     unimpl = ['OTHER_CODE_SIGN_FLAGS']
985     unimpl = set(unimpl) & set(self.xcode_settings[configname].keys())
986     if unimpl:
987       print 'Warning: Some codesign keys not implemented, ignoring: %s' % (
988           ', '.join(sorted(unimpl)))
989
990     return ['%s code-sign-bundle "%s" "%s" "%s" "%s"' % (
991         os.path.join('${TARGET_BUILD_DIR}', 'gyp-mac-tool'), key,
992         settings.get('CODE_SIGN_RESOURCE_RULES_PATH', ''),
993         settings.get('CODE_SIGN_ENTITLEMENTS', ''),
994         settings.get('PROVISIONING_PROFILE', ''))
995     ]
996
997   def _GetIOSCodeSignIdentityKey(self, settings):
998     identity = settings.get('CODE_SIGN_IDENTITY')
999     if not identity:
1000       return None
1001     if identity not in XcodeSettings._codesigning_key_cache:
1002       output = subprocess.check_output(
1003           ['security', 'find-identity', '-p', 'codesigning', '-v'])
1004       for line in output.splitlines():
1005         if identity in line:
1006           fingerprint = line.split()[1]
1007           cache = XcodeSettings._codesigning_key_cache
1008           assert identity not in cache or fingerprint == cache[identity], (
1009               "Multiple codesigning fingerprints for identity: %s" % identity)
1010           XcodeSettings._codesigning_key_cache[identity] = fingerprint
1011     return XcodeSettings._codesigning_key_cache.get(identity, '')
1012
1013   def AddImplicitPostbuilds(self, configname, output, output_binary,
1014                             postbuilds=[], quiet=False):
1015     """Returns a list of shell commands that should run before and after
1016     |postbuilds|."""
1017     assert output_binary is not None
1018     pre = self._GetTargetPostbuilds(configname, output, output_binary, quiet)
1019     post = self._GetIOSPostbuilds(configname, output_binary)
1020     return pre + postbuilds + post
1021
1022   def _AdjustLibrary(self, library, config_name=None):
1023     if library.endswith('.framework'):
1024       l = '-framework ' + os.path.splitext(os.path.basename(library))[0]
1025     else:
1026       m = self.library_re.match(library)
1027       if m:
1028         l = '-l' + m.group(1)
1029       else:
1030         l = library
1031
1032     sdk_root = self._SdkPath(config_name)
1033     if not sdk_root:
1034       sdk_root = ''
1035     # Xcode 7 started shipping with ".tbd" (text based stubs) files instead of
1036     # ".dylib" without providing a real support for them. What it does, for
1037     # "/usr/lib" libraries, is do "-L/usr/lib -lname" which is dependent on the
1038     # library order and cause collision when building Chrome.
1039     #
1040     # Instead substitude ".tbd" to ".dylib" in the generated project when the
1041     # following conditions are both true:
1042     # - library is referenced in the gyp file as "$(SDKROOT)/**/*.dylib",
1043     # - the ".dylib" file does not exists but a ".tbd" file do.
1044     library = l.replace('$(SDKROOT)', sdk_root)
1045     if l.startswith('$(SDKROOT)'):
1046       basename, ext = os.path.splitext(library)
1047       if ext == '.dylib' and not os.path.exists(library):
1048         tbd_library = basename + '.tbd'
1049         if os.path.exists(tbd_library):
1050           library = tbd_library
1051     return library
1052
1053   def AdjustLibraries(self, libraries, config_name=None):
1054     """Transforms entries like 'Cocoa.framework' in libraries into entries like
1055     '-framework Cocoa', 'libcrypto.dylib' into '-lcrypto', etc.
1056     """
1057     libraries = [self._AdjustLibrary(library, config_name)
1058                  for library in libraries]
1059     return libraries
1060
1061   def _BuildMachineOSBuild(self):
1062     return GetStdout(['sw_vers', '-buildVersion'])
1063
1064   def _XcodeIOSDeviceFamily(self, configname):
1065     family = self.xcode_settings[configname].get('TARGETED_DEVICE_FAMILY', '1')
1066     return [int(x) for x in family.split(',')]
1067
1068   def GetExtraPlistItems(self, configname=None):
1069     """Returns a dictionary with extra items to insert into Info.plist."""
1070     if configname not in XcodeSettings._plist_cache:
1071       cache = {}
1072       cache['BuildMachineOSBuild'] = self._BuildMachineOSBuild()
1073
1074       xcode, xcode_build = XcodeVersion()
1075       cache['DTXcode'] = xcode
1076       cache['DTXcodeBuild'] = xcode_build
1077
1078       sdk_root = self._SdkRoot(configname)
1079       if not sdk_root:
1080         sdk_root = self._DefaultSdkRoot()
1081       cache['DTSDKName'] = sdk_root
1082       if xcode >= '0430':
1083         cache['DTSDKBuild'] = self._GetSdkVersionInfoItem(
1084             sdk_root, 'ProductBuildVersion')
1085       else:
1086         cache['DTSDKBuild'] = cache['BuildMachineOSBuild']
1087
1088       if self.isIOS:
1089         cache['DTPlatformName'] = cache['DTSDKName']
1090         if configname.endswith("iphoneos"):
1091           cache['DTPlatformVersion'] = self._GetSdkVersionInfoItem(
1092               sdk_root, 'ProductVersion')
1093           cache['CFBundleSupportedPlatforms'] = ['iPhoneOS']
1094         else:
1095           cache['CFBundleSupportedPlatforms'] = ['iPhoneSimulator']
1096       XcodeSettings._plist_cache[configname] = cache
1097
1098     # Include extra plist items that are per-target, not per global
1099     # XcodeSettings.
1100     items = dict(XcodeSettings._plist_cache[configname])
1101     if self.isIOS:
1102       items['UIDeviceFamily'] = self._XcodeIOSDeviceFamily(configname)
1103     return items
1104
1105   def _DefaultSdkRoot(self):
1106     """Returns the default SDKROOT to use.
1107
1108     Prior to version 5.0.0, if SDKROOT was not explicitly set in the Xcode
1109     project, then the environment variable was empty. Starting with this
1110     version, Xcode uses the name of the newest SDK installed.
1111     """
1112     xcode_version, xcode_build = XcodeVersion()
1113     if xcode_version < '0500':
1114       return ''
1115     default_sdk_path = self._XcodeSdkPath('')
1116     default_sdk_root = XcodeSettings._sdk_root_cache.get(default_sdk_path)
1117     if default_sdk_root:
1118       return default_sdk_root
1119     try:
1120       all_sdks = GetStdout(['xcodebuild', '-showsdks'])
1121     except:
1122       # If xcodebuild fails, there will be no valid SDKs
1123       return ''
1124     for line in all_sdks.splitlines():
1125       items = line.split()
1126       if len(items) >= 3 and items[-2] == '-sdk':
1127         sdk_root = items[-1]
1128         sdk_path = self._XcodeSdkPath(sdk_root)
1129         if sdk_path == default_sdk_path:
1130           return sdk_root
1131     return ''
1132
1133
1134 class MacPrefixHeader(object):
1135   """A class that helps with emulating Xcode's GCC_PREFIX_HEADER feature.
1136
1137   This feature consists of several pieces:
1138   * If GCC_PREFIX_HEADER is present, all compilations in that project get an
1139     additional |-include path_to_prefix_header| cflag.
1140   * If GCC_PRECOMPILE_PREFIX_HEADER is present too, then the prefix header is
1141     instead compiled, and all other compilations in the project get an
1142     additional |-include path_to_compiled_header| instead.
1143     + Compiled prefix headers have the extension gch. There is one gch file for
1144       every language used in the project (c, cc, m, mm), since gch files for
1145       different languages aren't compatible.
1146     + gch files themselves are built with the target's normal cflags, but they
1147       obviously don't get the |-include| flag. Instead, they need a -x flag that
1148       describes their language.
1149     + All o files in the target need to depend on the gch file, to make sure
1150       it's built before any o file is built.
1151
1152   This class helps with some of these tasks, but it needs help from the build
1153   system for writing dependencies to the gch files, for writing build commands
1154   for the gch files, and for figuring out the location of the gch files.
1155   """
1156   def __init__(self, xcode_settings,
1157                gyp_path_to_build_path, gyp_path_to_build_output):
1158     """If xcode_settings is None, all methods on this class are no-ops.
1159
1160     Args:
1161         gyp_path_to_build_path: A function that takes a gyp-relative path,
1162             and returns a path relative to the build directory.
1163         gyp_path_to_build_output: A function that takes a gyp-relative path and
1164             a language code ('c', 'cc', 'm', or 'mm'), and that returns a path
1165             to where the output of precompiling that path for that language
1166             should be placed (without the trailing '.gch').
1167     """
1168     # This doesn't support per-configuration prefix headers. Good enough
1169     # for now.
1170     self.header = None
1171     self.compile_headers = False
1172     if xcode_settings:
1173       self.header = xcode_settings.GetPerTargetSetting('GCC_PREFIX_HEADER')
1174       self.compile_headers = xcode_settings.GetPerTargetSetting(
1175           'GCC_PRECOMPILE_PREFIX_HEADER', default='NO') != 'NO'
1176     self.compiled_headers = {}
1177     if self.header:
1178       if self.compile_headers:
1179         for lang in ['c', 'cc', 'm', 'mm']:
1180           self.compiled_headers[lang] = gyp_path_to_build_output(
1181               self.header, lang)
1182       self.header = gyp_path_to_build_path(self.header)
1183
1184   def _CompiledHeader(self, lang, arch):
1185     assert self.compile_headers
1186     h = self.compiled_headers[lang]
1187     if arch:
1188       h += '.' + arch
1189     return h
1190
1191   def GetInclude(self, lang, arch=None):
1192     """Gets the cflags to include the prefix header for language |lang|."""
1193     if self.compile_headers and lang in self.compiled_headers:
1194       return '-include %s' % self._CompiledHeader(lang, arch)
1195     elif self.header:
1196       return '-include %s' % self.header
1197     else:
1198       return ''
1199
1200   def _Gch(self, lang, arch):
1201     """Returns the actual file name of the prefix header for language |lang|."""
1202     assert self.compile_headers
1203     return self._CompiledHeader(lang, arch) + '.gch'
1204
1205   def GetObjDependencies(self, sources, objs, arch=None):
1206     """Given a list of source files and the corresponding object files, returns
1207     a list of (source, object, gch) tuples, where |gch| is the build-directory
1208     relative path to the gch file each object file depends on.  |compilable[i]|
1209     has to be the source file belonging to |objs[i]|."""
1210     if not self.header or not self.compile_headers:
1211       return []
1212
1213     result = []
1214     for source, obj in zip(sources, objs):
1215       ext = os.path.splitext(source)[1]
1216       lang = {
1217         '.c': 'c',
1218         '.cpp': 'cc', '.cc': 'cc', '.cxx': 'cc',
1219         '.m': 'm',
1220         '.mm': 'mm',
1221       }.get(ext, None)
1222       if lang:
1223         result.append((source, obj, self._Gch(lang, arch)))
1224     return result
1225
1226   def GetPchBuildCommands(self, arch=None):
1227     """Returns [(path_to_gch, language_flag, language, header)].
1228     |path_to_gch| and |header| are relative to the build directory.
1229     """
1230     if not self.header or not self.compile_headers:
1231       return []
1232     return [
1233       (self._Gch('c', arch), '-x c-header', 'c', self.header),
1234       (self._Gch('cc', arch), '-x c++-header', 'cc', self.header),
1235       (self._Gch('m', arch), '-x objective-c-header', 'm', self.header),
1236       (self._Gch('mm', arch), '-x objective-c++-header', 'mm', self.header),
1237     ]
1238
1239
1240 def XcodeVersion():
1241   """Returns a tuple of version and build version of installed Xcode."""
1242   # `xcodebuild -version` output looks like
1243   #    Xcode 4.6.3
1244   #    Build version 4H1503
1245   # or like
1246   #    Xcode 3.2.6
1247   #    Component versions: DevToolsCore-1809.0; DevToolsSupport-1806.0
1248   #    BuildVersion: 10M2518
1249   # Convert that to '0463', '4H1503'.
1250   global XCODE_VERSION_CACHE
1251   if XCODE_VERSION_CACHE:
1252     return XCODE_VERSION_CACHE
1253   try:
1254     version_list = GetStdout(['xcodebuild', '-version']).splitlines()
1255     # In some circumstances xcodebuild exits 0 but doesn't return
1256     # the right results; for example, a user on 10.7 or 10.8 with
1257     # a bogus path set via xcode-select
1258     # In that case this may be a CLT-only install so fall back to
1259     # checking that version.
1260     if len(version_list) < 2:
1261       raise GypError("xcodebuild returned unexpected results")
1262   except:
1263     version = CLTVersion()
1264     if version:
1265       version = re.match(r'(\d\.\d\.?\d*)', version).groups()[0]
1266     else:
1267       raise GypError("No Xcode or CLT version detected!")
1268     # The CLT has no build information, so we return an empty string.
1269     version_list = [version, '']
1270   version = version_list[0]
1271   build = version_list[-1]
1272   # Be careful to convert "4.2" to "0420":
1273   version = version.split()[-1].replace('.', '')
1274   version = (version + '0' * (3 - len(version))).zfill(4)
1275   if build:
1276     build = build.split()[-1]
1277   XCODE_VERSION_CACHE = (version, build)
1278   return XCODE_VERSION_CACHE
1279
1280
1281 # This function ported from the logic in Homebrew's CLT version check
1282 def CLTVersion():
1283   """Returns the version of command-line tools from pkgutil."""
1284   # pkgutil output looks like
1285   #   package-id: com.apple.pkg.CLTools_Executables
1286   #   version: 5.0.1.0.1.1382131676
1287   #   volume: /
1288   #   location: /
1289   #   install-time: 1382544035
1290   #   groups: com.apple.FindSystemFiles.pkg-group com.apple.DevToolsBoth.pkg-group com.apple.DevToolsNonRelocatableShared.pkg-group
1291   STANDALONE_PKG_ID = "com.apple.pkg.DeveloperToolsCLILeo"
1292   FROM_XCODE_PKG_ID = "com.apple.pkg.DeveloperToolsCLI"
1293   MAVERICKS_PKG_ID = "com.apple.pkg.CLTools_Executables"
1294
1295   regex = re.compile('version: (?P<version>.+)')
1296   for key in [MAVERICKS_PKG_ID, STANDALONE_PKG_ID, FROM_XCODE_PKG_ID]:
1297     try:
1298       output = GetStdout(['/usr/sbin/pkgutil', '--pkg-info', key])
1299       return re.search(regex, output).groupdict()['version']
1300     except:
1301       continue
1302
1303
1304 def GetStdout(cmdlist):
1305   """Returns the content of standard output returned by invoking |cmdlist|.
1306   Raises |GypError| if the command return with a non-zero return code."""
1307   job = subprocess.Popen(cmdlist, stdout=subprocess.PIPE)
1308   out = job.communicate()[0]
1309   if job.returncode != 0:
1310     sys.stderr.write(out + '\n')
1311     raise GypError('Error %d running %s' % (job.returncode, cmdlist[0]))
1312   return out.rstrip('\n')
1313
1314
1315 def MergeGlobalXcodeSettingsToSpec(global_dict, spec):
1316   """Merges the global xcode_settings dictionary into each configuration of the
1317   target represented by spec. For keys that are both in the global and the local
1318   xcode_settings dict, the local key gets precendence.
1319   """
1320   # The xcode generator special-cases global xcode_settings and does something
1321   # that amounts to merging in the global xcode_settings into each local
1322   # xcode_settings dict.
1323   global_xcode_settings = global_dict.get('xcode_settings', {})
1324   for config in spec['configurations'].values():
1325     if 'xcode_settings' in config:
1326       new_settings = global_xcode_settings.copy()
1327       new_settings.update(config['xcode_settings'])
1328       config['xcode_settings'] = new_settings
1329
1330
1331 def IsMacBundle(flavor, spec):
1332   """Returns if |spec| should be treated as a bundle.
1333
1334   Bundles are directories with a certain subdirectory structure, instead of
1335   just a single file. Bundle rules do not produce a binary but also package
1336   resources into that directory."""
1337   is_mac_bundle = (int(spec.get('mac_bundle', 0)) != 0 and flavor == 'mac')
1338   if is_mac_bundle:
1339     assert spec['type'] != 'none', (
1340         'mac_bundle targets cannot have type none (target "%s")' %
1341         spec['target_name'])
1342   return is_mac_bundle
1343
1344
1345 def GetMacBundleResources(product_dir, xcode_settings, resources):
1346   """Yields (output, resource) pairs for every resource in |resources|.
1347   Only call this for mac bundle targets.
1348
1349   Args:
1350       product_dir: Path to the directory containing the output bundle,
1351           relative to the build directory.
1352       xcode_settings: The XcodeSettings of the current target.
1353       resources: A list of bundle resources, relative to the build directory.
1354   """
1355   dest = os.path.join(product_dir,
1356                       xcode_settings.GetBundleResourceFolder())
1357   for res in resources:
1358     output = dest
1359
1360     # The make generator doesn't support it, so forbid it everywhere
1361     # to keep the generators more interchangable.
1362     assert ' ' not in res, (
1363       "Spaces in resource filenames not supported (%s)"  % res)
1364
1365     # Split into (path,file).
1366     res_parts = os.path.split(res)
1367
1368     # Now split the path into (prefix,maybe.lproj).
1369     lproj_parts = os.path.split(res_parts[0])
1370     # If the resource lives in a .lproj bundle, add that to the destination.
1371     if lproj_parts[1].endswith('.lproj'):
1372       output = os.path.join(output, lproj_parts[1])
1373
1374     output = os.path.join(output, res_parts[1])
1375     # Compiled XIB files are referred to by .nib.
1376     if output.endswith('.xib'):
1377       output = os.path.splitext(output)[0] + '.nib'
1378     # Compiled storyboard files are referred to by .storyboardc.
1379     if output.endswith('.storyboard'):
1380       output = os.path.splitext(output)[0] + '.storyboardc'
1381
1382     yield output, res
1383
1384
1385 def GetMacInfoPlist(product_dir, xcode_settings, gyp_path_to_build_path):
1386   """Returns (info_plist, dest_plist, defines, extra_env), where:
1387   * |info_plist| is the source plist path, relative to the
1388     build directory,
1389   * |dest_plist| is the destination plist path, relative to the
1390     build directory,
1391   * |defines| is a list of preprocessor defines (empty if the plist
1392     shouldn't be preprocessed,
1393   * |extra_env| is a dict of env variables that should be exported when
1394     invoking |mac_tool copy-info-plist|.
1395
1396   Only call this for mac bundle targets.
1397
1398   Args:
1399       product_dir: Path to the directory containing the output bundle,
1400           relative to the build directory.
1401       xcode_settings: The XcodeSettings of the current target.
1402       gyp_to_build_path: A function that converts paths relative to the
1403           current gyp file to paths relative to the build direcotry.
1404   """
1405   info_plist = xcode_settings.GetPerTargetSetting('INFOPLIST_FILE')
1406   if not info_plist:
1407     return None, None, [], {}
1408
1409   # The make generator doesn't support it, so forbid it everywhere
1410   # to keep the generators more interchangable.
1411   assert ' ' not in info_plist, (
1412     "Spaces in Info.plist filenames not supported (%s)"  % info_plist)
1413
1414   info_plist = gyp_path_to_build_path(info_plist)
1415
1416   # If explicitly set to preprocess the plist, invoke the C preprocessor and
1417   # specify any defines as -D flags.
1418   if xcode_settings.GetPerTargetSetting(
1419       'INFOPLIST_PREPROCESS', default='NO') == 'YES':
1420     # Create an intermediate file based on the path.
1421     defines = shlex.split(xcode_settings.GetPerTargetSetting(
1422         'INFOPLIST_PREPROCESSOR_DEFINITIONS', default=''))
1423   else:
1424     defines = []
1425
1426   dest_plist = os.path.join(product_dir, xcode_settings.GetBundlePlistPath())
1427   extra_env = xcode_settings.GetPerTargetSettings()
1428
1429   return info_plist, dest_plist, defines, extra_env
1430
1431
1432 def _GetXcodeEnv(xcode_settings, built_products_dir, srcroot, configuration,
1433                 additional_settings=None):
1434   """Return the environment variables that Xcode would set. See
1435   http://developer.apple.com/library/mac/#documentation/DeveloperTools/Reference/XcodeBuildSettingRef/1-Build_Setting_Reference/build_setting_ref.html#//apple_ref/doc/uid/TP40003931-CH3-SW153
1436   for a full list.
1437
1438   Args:
1439       xcode_settings: An XcodeSettings object. If this is None, this function
1440           returns an empty dict.
1441       built_products_dir: Absolute path to the built products dir.
1442       srcroot: Absolute path to the source root.
1443       configuration: The build configuration name.
1444       additional_settings: An optional dict with more values to add to the
1445           result.
1446   """
1447   if not xcode_settings: return {}
1448
1449   # This function is considered a friend of XcodeSettings, so let it reach into
1450   # its implementation details.
1451   spec = xcode_settings.spec
1452
1453   # These are filled in on a as-needed basis.
1454   env = {
1455     'BUILT_FRAMEWORKS_DIR' : built_products_dir,
1456     'BUILT_PRODUCTS_DIR' : built_products_dir,
1457     'CONFIGURATION' : configuration,
1458     'PRODUCT_NAME' : xcode_settings.GetProductName(),
1459     # See /Developer/Platforms/MacOSX.platform/Developer/Library/Xcode/Specifications/MacOSX\ Product\ Types.xcspec for FULL_PRODUCT_NAME
1460     'SRCROOT' : srcroot,
1461     'SOURCE_ROOT': '${SRCROOT}',
1462     # This is not true for static libraries, but currently the env is only
1463     # written for bundles:
1464     'TARGET_BUILD_DIR' : built_products_dir,
1465     'TEMP_DIR' : '${TMPDIR}',
1466   }
1467   if xcode_settings.GetPerConfigSetting('SDKROOT', configuration):
1468     env['SDKROOT'] = xcode_settings._SdkPath(configuration)
1469   else:
1470     env['SDKROOT'] = ''
1471
1472   if spec['type'] in (
1473       'executable', 'static_library', 'shared_library', 'loadable_module'):
1474     env['EXECUTABLE_NAME'] = xcode_settings.GetExecutableName()
1475     env['EXECUTABLE_PATH'] = xcode_settings.GetExecutablePath()
1476     env['FULL_PRODUCT_NAME'] = xcode_settings.GetFullProductName()
1477     mach_o_type = xcode_settings.GetMachOType()
1478     if mach_o_type:
1479       env['MACH_O_TYPE'] = mach_o_type
1480     env['PRODUCT_TYPE'] = xcode_settings.GetProductType()
1481   if xcode_settings._IsBundle():
1482     env['CONTENTS_FOLDER_PATH'] = \
1483       xcode_settings.GetBundleContentsFolderPath()
1484     env['UNLOCALIZED_RESOURCES_FOLDER_PATH'] = \
1485         xcode_settings.GetBundleResourceFolder()
1486     env['INFOPLIST_PATH'] = xcode_settings.GetBundlePlistPath()
1487     env['WRAPPER_NAME'] = xcode_settings.GetWrapperName()
1488
1489   install_name = xcode_settings.GetInstallName()
1490   if install_name:
1491     env['LD_DYLIB_INSTALL_NAME'] = install_name
1492   install_name_base = xcode_settings.GetInstallNameBase()
1493   if install_name_base:
1494     env['DYLIB_INSTALL_NAME_BASE'] = install_name_base
1495   if XcodeVersion() >= '0500' and not env.get('SDKROOT'):
1496     sdk_root = xcode_settings._SdkRoot(configuration)
1497     if not sdk_root:
1498       sdk_root = xcode_settings._XcodeSdkPath('')
1499     if sdk_root is None:
1500       sdk_root = ''
1501     env['SDKROOT'] = sdk_root
1502
1503   if not additional_settings:
1504     additional_settings = {}
1505   else:
1506     # Flatten lists to strings.
1507     for k in additional_settings:
1508       if not isinstance(additional_settings[k], str):
1509         additional_settings[k] = ' '.join(additional_settings[k])
1510   additional_settings.update(env)
1511
1512   for k in additional_settings:
1513     additional_settings[k] = _NormalizeEnvVarReferences(additional_settings[k])
1514
1515   return additional_settings
1516
1517
1518 def _NormalizeEnvVarReferences(str):
1519   """Takes a string containing variable references in the form ${FOO}, $(FOO),
1520   or $FOO, and returns a string with all variable references in the form ${FOO}.
1521   """
1522   # $FOO -> ${FOO}
1523   str = re.sub(r'\$([a-zA-Z_][a-zA-Z0-9_]*)', r'${\1}', str)
1524
1525   # $(FOO) -> ${FOO}
1526   matches = re.findall(r'(\$\(([a-zA-Z0-9\-_]+)\))', str)
1527   for match in matches:
1528     to_replace, variable = match
1529     assert '$(' not in match, '$($(FOO)) variables not supported: ' + match
1530     str = str.replace(to_replace, '${' + variable + '}')
1531
1532   return str
1533
1534
1535 def ExpandEnvVars(string, expansions):
1536   """Expands ${VARIABLES}, $(VARIABLES), and $VARIABLES in string per the
1537   expansions list. If the variable expands to something that references
1538   another variable, this variable is expanded as well if it's in env --
1539   until no variables present in env are left."""
1540   for k, v in reversed(expansions):
1541     string = string.replace('${' + k + '}', v)
1542     string = string.replace('$(' + k + ')', v)
1543     string = string.replace('$' + k, v)
1544   return string
1545
1546
1547 def _TopologicallySortedEnvVarKeys(env):
1548   """Takes a dict |env| whose values are strings that can refer to other keys,
1549   for example env['foo'] = '$(bar) and $(baz)'. Returns a list L of all keys of
1550   env such that key2 is after key1 in L if env[key2] refers to env[key1].
1551
1552   Throws an Exception in case of dependency cycles.
1553   """
1554   # Since environment variables can refer to other variables, the evaluation
1555   # order is important. Below is the logic to compute the dependency graph
1556   # and sort it.
1557   regex = re.compile(r'\$\{([a-zA-Z0-9\-_]+)\}')
1558   def GetEdges(node):
1559     # Use a definition of edges such that user_of_variable -> used_varible.
1560     # This happens to be easier in this case, since a variable's
1561     # definition contains all variables it references in a single string.
1562     # We can then reverse the result of the topological sort at the end.
1563     # Since: reverse(topsort(DAG)) = topsort(reverse_edges(DAG))
1564     matches = set([v for v in regex.findall(env[node]) if v in env])
1565     for dependee in matches:
1566       assert '${' not in dependee, 'Nested variables not supported: ' + dependee
1567     return matches
1568
1569   try:
1570     # Topologically sort, and then reverse, because we used an edge definition
1571     # that's inverted from the expected result of this function (see comment
1572     # above).
1573     order = gyp.common.TopologicallySorted(env.keys(), GetEdges)
1574     order.reverse()
1575     return order
1576   except gyp.common.CycleError, e:
1577     raise GypError(
1578         'Xcode environment variables are cyclically dependent: ' + str(e.nodes))
1579
1580
1581 def GetSortedXcodeEnv(xcode_settings, built_products_dir, srcroot,
1582                       configuration, additional_settings=None):
1583   env = _GetXcodeEnv(xcode_settings, built_products_dir, srcroot, configuration,
1584                     additional_settings)
1585   return [(key, env[key]) for key in _TopologicallySortedEnvVarKeys(env)]
1586
1587
1588 def GetSpecPostbuildCommands(spec, quiet=False):
1589   """Returns the list of postbuilds explicitly defined on |spec|, in a form
1590   executable by a shell."""
1591   postbuilds = []
1592   for postbuild in spec.get('postbuilds', []):
1593     if not quiet:
1594       postbuilds.append('echo POSTBUILD\\(%s\\) %s' % (
1595             spec['target_name'], postbuild['postbuild_name']))
1596     postbuilds.append(gyp.common.EncodePOSIXShellList(postbuild['action']))
1597   return postbuilds
1598
1599
1600 def _HasIOSTarget(targets):
1601   """Returns true if any target contains the iOS specific key
1602   IPHONEOS_DEPLOYMENT_TARGET."""
1603   for target_dict in targets.values():
1604     for config in target_dict['configurations'].values():
1605       if config.get('xcode_settings', {}).get('IPHONEOS_DEPLOYMENT_TARGET'):
1606         return True
1607   return False
1608
1609
1610 def _AddIOSDeviceConfigurations(targets):
1611   """Clone all targets and append -iphoneos to the name. Configure these targets
1612   to build for iOS devices and use correct architectures for those builds."""
1613   for target_dict in targets.itervalues():
1614     toolset = target_dict['toolset']
1615     configs = target_dict['configurations']
1616     for config_name, config_dict in dict(configs).iteritems():
1617       iphoneos_config_dict = copy.deepcopy(config_dict)
1618       configs[config_name + '-iphoneos'] = iphoneos_config_dict
1619       configs[config_name + '-iphonesimulator'] = config_dict
1620       if toolset == 'target':
1621         iphoneos_config_dict['xcode_settings']['SDKROOT'] = 'iphoneos'
1622   return targets
1623
1624 def CloneConfigurationForDeviceAndEmulator(target_dicts):
1625   """If |target_dicts| contains any iOS targets, automatically create -iphoneos
1626   targets for iOS device builds."""
1627   if _HasIOSTarget(target_dicts):
1628     return _AddIOSDeviceConfigurations(target_dicts)
1629   return target_dicts