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