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