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