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