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