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