• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 _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
1089class 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
1195def 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
1237def 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
1259def 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
1270def 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
1286def 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
1300def 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
1340def 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
1387def _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
1470def _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
1487def 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
1499def _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
1533def 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
1540def 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
1552def _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
1562def _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
1576def 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
1582