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