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