gyp: update to 78b26f7
[platform/upstream/nodejs.git] / tools / gyp / pylib / gyp / xcode_emulation.py
1 # Copyright (c) 2012 Google Inc. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
4
5 """
6 This module contains classes that help to emulate xcodebuild behavior on top of
7 other build systems, such as make and ninja.
8 """
9
10 import copy
11 import gyp.common
12 import os
13 import os.path
14 import re
15 import shlex
16 import subprocess
17 import sys
18 import tempfile
19 from gyp.common import GypError
20
21 class 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
966 class 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
1072 def 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
1088 def 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
1102 def 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
1142 def 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
1189 def _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
1267 def _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
1284 def 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
1296 def _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
1330 def 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
1337 def 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
1349 def _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
1359 def _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
1373 def 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