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.
6 This module contains classes that help to emulate xcodebuild behavior on top of
7 other build systems, such as make and ninja.
16 from gyp.common import GypError
18 class XcodeSettings(object):
19 """A class that understands the gyp 'xcode_settings' object."""
21 # Populated lazily by _SdkPath(). Shared by all XcodeSettings, so cached
22 # at class-level for efficiency.
25 def __init__(self, spec):
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', {})
37 # This is only non-None temporarily during the execution of some methods.
38 self.configname = None
40 # Used by _AdjustLibrary to match .a and .dylib entries in libraries.
41 self.library_re = re.compile(r'^lib([^/]+)\.(a|dylib)$')
44 assert self.configname
45 return self.xcode_settings[self.configname]
47 def _Test(self, test_key, cond_key, default):
48 return self._Settings().get(test_key, default) == cond_key
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]))
54 lst.append(format_str % str(default))
56 def _WarnUnimplemented(self, test_key):
57 if test_key in self._Settings():
58 print 'Warning: Ignoring not yet implemented key "%s".' % test_key
61 return int(self.spec.get('mac_bundle', 0)) != 0
63 def GetFrameworkVersion(self):
64 """Returns the framework version of the current target. Only valid for
66 assert self._IsBundle()
67 return self.GetPerTargetSetting('FRAMEWORK_VERSION', default='A')
69 def GetWrapperExtension(self):
70 """Returns the bundle extension (.app, .framework, .plugin, etc). Only
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',
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':
84 assert False, "Don't know extension for '%s', target '%s'" % (
85 self.spec['type'], self.spec['target_name'])
87 def GetProductName(self):
88 """Returns PRODUCT_NAME."""
89 return self.spec.get('product_name', self.spec['target_name'])
91 def GetFullProductName(self):
92 """Returns FULL_PRODUCT_NAME."""
94 return self.GetWrapperName()
96 return self._GetStandaloneBinaryPath()
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()
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':
110 self.GetWrapperName(), 'Versions', self.GetFrameworkVersion())
112 # loadable_modules have a 'Contents' folder like executables.
113 return os.path.join(self.GetWrapperName(), 'Contents')
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')
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')
128 return os.path.join(self.GetBundleContentsFolderPath(),
129 'Resources', 'Info.plist')
131 def GetProductType(self):
132 """Returns the PRODUCT_TYPE of this target."""
135 'executable': 'com.apple.product-type.application',
136 'loadable_module': 'com.apple.product-type.bundle',
137 'shared_library': 'com.apple.product-type.framework',
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',
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':
153 'executable': 'mh_execute',
154 'static_library': 'staticlib',
155 'shared_library': 'mh_dylib',
156 'loadable_module': 'mh_bundle',
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())
169 def _GetStandaloneExecutableSuffix(self):
170 if 'product_extension' in self.spec:
171 return '.' + self.spec['product_extension']
174 'static_library': '.a',
175 'shared_library': '.dylib',
176 'loadable_module': '.so',
179 def _GetStandaloneExecutablePrefix(self):
180 return self.spec.get('product_prefix', {
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']])
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':
200 elif self.spec['type'] in ('loadable_module', 'shared_library'):
201 if target[:3] == 'lib':
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
209 def GetExecutableName(self):
210 """Returns the executable name of the bundle represented by this target.
213 return self.spec.get('product_name', self.spec['target_name'])
215 return self._GetStandaloneBinaryPath()
217 def GetExecutablePath(self):
218 """Returns the directory name of the bundle represented by this target. E.g.
219 Chromium.app/Contents/MacOS/Chromium."""
221 return self._GetBundleBinaryPath()
223 return self._GetStandaloneBinaryPath()
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')
235 sdk_root = self.GetPerTargetSetting('SDKROOT', default='macosx')
236 if sdk_root.startswith('/'):
238 if sdk_root not in XcodeSettings._sdk_path_cache:
239 XcodeSettings._sdk_path_cache[sdk_root] = self._GetSdkVersionInfoItem(
241 return XcodeSettings._sdk_path_cache[sdk_root]
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')
252 self._Appendf(lst, 'IPHONEOS_DEPLOYMENT_TARGET',
253 '-miphoneos-version-min=%s')
255 def GetCflags(self, configname):
256 """Returns flags that need to be added to .c, .cc, .m, and .mm
258 # This functions (and the similar ones below) do not offer complete
259 # emulation of all xcode_settings keys. They're implemented on demand.
261 self.configname = configname
264 sdk_root = self._SdkPath()
265 if 'SDKROOT' in self._Settings():
266 cflags.append('-isysroot %s' % sdk_root)
268 if self._Test('CLANG_WARN_CONSTANT_CONVERSION', 'YES', default='NO'):
269 cflags.append('-Wconstant-conversion')
271 if self._Test('GCC_CHAR_IS_UNSIGNED_CHAR', 'YES', default='NO'):
272 cflags.append('-funsigned-char')
274 if self._Test('GCC_CW_ASM_SYNTAX', 'YES', default='YES'):
275 cflags.append('-fasm-blocks')
277 if 'GCC_DYNAMIC_NO_PIC' in self._Settings():
278 if self._Settings()['GCC_DYNAMIC_NO_PIC'] == 'YES':
279 cflags.append('-mdynamic-no-pic')
282 # TODO: In this case, it depends on the target. xcode passes
283 # mdynamic-no-pic by default for executable and possibly static lib
286 if self._Test('GCC_ENABLE_PASCAL_STRINGS', 'YES', default='YES'):
287 cflags.append('-mpascal-strings')
289 self._Appendf(cflags, 'GCC_OPTIMIZATION_LEVEL', '-O%s', default='s')
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')
300 raise NotImplementedError('Unknown debug format %s' % dbg_format)
302 if self._Test('GCC_SYMBOLS_PRIVATE_EXTERN', 'YES', default='NO'):
303 cflags.append('-fvisibility=hidden')
305 if self._Test('GCC_TREAT_WARNINGS_AS_ERRORS', 'YES', default='NO'):
306 cflags.append('-Werror')
308 if self._Test('GCC_WARN_ABOUT_MISSING_NEWLINE', 'YES', default='NO'):
309 cflags.append('-Wnewline-eof')
311 self._AppendPlatformVersionMinFlags(cflags)
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')
319 # TODO: This is exported correctly, but assigning to it is not supported.
320 self._WarnUnimplemented('MACH_O_TYPE')
321 self._WarnUnimplemented('PRODUCT_TYPE')
323 archs = self._Settings().get('ARCHS', ['i386'])
325 # TODO: Supporting fat binaries will be annoying.
326 self._WarnUnimplemented('ARCHS')
328 cflags.append('-arch ' + archs[0])
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',
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')
341 cflags += self._Settings().get('WARNING_CFLAGS', [])
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))
348 self.configname = None
351 def GetCflagsC(self, configname):
352 """Returns flags that need to be added to .c, and .m compilations."""
353 self.configname = configname
355 self._Appendf(cflags_c, 'GCC_C_LANGUAGE_STANDARD', '-std=%s')
356 cflags_c += self._Settings().get('OTHER_CFLAGS', [])
357 self.configname = None
360 def GetCflagsCC(self, configname):
361 """Returns flags that need to be added to .cc, and .mm compilations."""
362 self.configname = configname
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)
372 self._Appendf(cflags_cc, 'CLANG_CXX_LIBRARY', '-stdlib=%s')
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')
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', [])
395 other_ccflags.append(flag)
396 cflags_cc += other_ccflags
398 self.configname = None
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')
408 def GetCflagsObjC(self, configname):
409 """Returns flags that need to be added to .m compilations."""
410 self.configname = configname
413 self._AddObjectiveCGarbageCollectionFlags(cflags_objc)
415 self.configname = None
418 def GetCflagsObjCC(self, configname):
419 """Returns flags that need to be added to .mm compilations."""
420 self.configname = configname
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
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())):
434 install_base = self.GetPerTargetSetting(
435 'DYLIB_INSTALL_NAME_BASE',
436 default='/Library/Frameworks' if self._IsBundle() else '/usr/local/lib')
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.
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)
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())):
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)
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))
472 install_name = install_name.replace(
473 '$(DYLIB_INSTALL_NAME_BASE:standardizepath)',
474 self._StandardizePath(self.GetInstallNameBase()))
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())
482 assert '$(WRAPPER_NAME)' not in install_name
483 assert '$(PRODUCT_NAME)' not in install_name
485 install_name = install_name.replace(
486 '$(EXECUTABLE_PATH)', self.GetExecutablePath())
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+)'
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.
505 for flag_pattern in linker_flags:
506 regex = re.compile('(?:-Wl,)?' + '[ ,]'.join(flag_pattern))
507 m = regex.match(ldflag)
509 ldflag = ldflag[:m.start(1)] + gyp_to_build_path(m.group(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'):])
517 def GetLdflags(self, configname, product_dir, gyp_to_build_path):
518 """Returns flags that need to be passed to the linker.
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.
527 self.configname = configname
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))
535 if self._Test('DEAD_CODE_STRIPPING', 'YES', default='NO'):
536 ldflags.append('-Wl,-dead_strip')
538 if self._Test('PREBINDING', 'YES', default='NO'):
539 ldflags.append('-Wl,-prebind')
542 ldflags, 'DYLIB_COMPATIBILITY_VERSION', '-compatibility_version %s')
544 ldflags, 'DYLIB_CURRENT_VERSION', '-current_version %s')
546 self._AppendPlatformVersionMinFlags(ldflags)
548 if 'SDKROOT' in self._Settings():
549 ldflags.append('-isysroot ' + self._SdkPath())
551 for library_path in self._Settings().get('LIBRARY_SEARCH_PATHS', []):
552 ldflags.append('-L' + gyp_to_build_path(library_path))
554 if 'ORDER_FILE' in self._Settings():
555 ldflags.append('-Wl,-order_file ' +
556 '-Wl,' + gyp_to_build_path(
557 self._Settings()['ORDER_FILE']))
559 archs = self._Settings().get('ARCHS', ['i386'])
561 # TODO: Supporting fat binaries will be annoying.
562 self._WarnUnimplemented('ARCHS')
564 ldflags.append('-arch ' + archs[0])
566 # Xcode adds the product directory by default.
567 ldflags.append('-L' + product_dir)
569 install_name = self.GetInstallName()
571 ldflags.append('-install_name ' + install_name.replace(' ', r'\ '))
573 for rpath in self._Settings().get('LD_RUNPATH_SEARCH_PATHS', []):
574 ldflags.append('-Wl,-rpath,' + rpath)
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()))
581 self.configname = None
584 def GetLibtoolflags(self, configname):
585 """Returns flags that need to be passed to the static linker.
588 configname: The name of the configuration to get ld flags for.
590 self.configname = configname
593 for libtoolflag in self._Settings().get('OTHER_LDFLAGS', []):
594 libtoolflags.append(libtoolflag)
595 # TODO(thakis): ARCHS?
597 self.configname = None
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."""
605 for configname in sorted(self.xcode_settings.keys()):
607 result = dict(self.xcode_settings[configname])
610 for key, value in self.xcode_settings[configname].iteritems():
611 if key not in result:
613 elif result[key] != value:
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."""
622 for configname in sorted(self.xcode_settings.keys()):
624 result = self.xcode_settings[configname].get(setting, None)
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']))
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
641 if (self._Test('DEPLOYMENT_POSTPROCESSING', 'YES', default='NO') and
642 self._Test('STRIP_INSTALLED_PRODUCT', 'YES', default='NO')):
644 default_strip_style = 'debugging'
646 default_strip_style = 'non-global'
647 elif self.spec['type'] == 'executable':
648 default_strip_style = 'all'
650 strip_style = self._Settings().get('STRIP_STYLE', default_strip_style)
657 explicit_strip_flags = self._Settings().get('STRIPFLAGS', '')
658 if explicit_strip_flags:
659 strip_flags += ' ' + _NormalizeEnvVarReferences(explicit_strip_flags)
662 result.append('echo STRIP\\(%s\\)' % self.spec['target_name'])
663 result.append('strip %s %s' % (strip_flags, output_binary))
665 self.configname = None
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
674 # For static libraries, no dSYMs are created.
676 if (self._Test('GCC_GENERATE_DEBUGGING_SYMBOLS', 'YES', default='YES') and
678 'DEBUG_INFORMATION_FORMAT', 'dwarf-with-dsym', default='dwarf') and
679 self.spec['type'] != 'static_library'):
681 result.append('echo DSYMUTIL\\(%s\\)' % self.spec['target_name'])
682 result.append('dsymutil %s -o %s' % (output_binary, output + '.dSYM'))
684 self.configname = None
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.
692 self._GetDebugInfoPostbuilds(configname, output, output_binary, quiet) +
693 self._GetStripPostbuilds(configname, output_binary, quiet))
695 def _AdjustLibrary(self, library):
696 if library.endswith('.framework'):
697 l = '-framework ' + os.path.splitext(os.path.basename(library))[0]
699 m = self.library_re.match(library)
701 l = '-l' + m.group(1)
704 return l.replace('$(SDKROOT)', self._SdkPath())
706 def AdjustLibraries(self, libraries):
707 """Transforms entries like 'Cocoa.framework' in libraries into entries like
708 '-framework Cocoa', 'libcrypto.dylib' into '-lcrypto', etc.
710 libraries = [ self._AdjustLibrary(library) for library in libraries]
714 class MacPrefixHeader(object):
715 """A class that helps with emulating Xcode's GCC_PREFIX_HEADER feature.
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.
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.
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.
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').
748 # This doesn't support per-configuration prefix headers. Good enough
751 self.compile_headers = False
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 = {}
758 if self.compile_headers:
759 for lang in ['c', 'cc', 'm', 'mm']:
760 self.compiled_headers[lang] = gyp_path_to_build_output(
762 self.header = gyp_path_to_build_path(self.header)
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]
769 return '-include %s' % self.header
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'
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:
787 for source, obj in zip(sources, objs):
788 ext = os.path.splitext(source)[1]
791 '.cpp': 'cc', '.cc': 'cc', '.cxx': 'cc',
796 result.append((source, obj, self._Gch(lang)))
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.
803 if not self.header or not self.compile_headers:
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),
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.
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
829 def IsMacBundle(flavor, spec):
830 """Returns if |spec| should be treated as a bundle.
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')
837 assert spec['type'] != 'none', (
838 'mac_bundle targets cannot have type none (target "%s")' %
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.
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.
853 dest = os.path.join(product_dir,
854 xcode_settings.GetBundleResourceFolder())
855 for res in resources:
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)
863 # Split into (path,file).
864 res_parts = os.path.split(res)
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])
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'
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
884 * |dest_plist| is the destination plist path, relative to the
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|.
891 Only call this for mac bundle targets.
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.
900 info_plist = xcode_settings.GetPerTargetSetting('INFOPLIST_FILE')
902 return None, None, [], {}
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)
909 info_plist = gyp_path_to_build_path(info_plist)
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=''))
921 dest_plist = os.path.join(product_dir, xcode_settings.GetBundlePlistPath())
922 extra_env = xcode_settings.GetPerTargetSettings()
924 return info_plist, dest_plist, defines, extra_env
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
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
942 if not xcode_settings: return {}
944 # This function is considered a friend of XcodeSettings, so let it reach into
945 # its implementation details.
946 spec = xcode_settings.spec
948 # These are filled in on a as-needed basis.
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
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}',
961 if xcode_settings.GetPerTargetSetting('SDKROOT'):
962 env['SDKROOT'] = xcode_settings._SdkPath()
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()
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()
983 install_name = xcode_settings.GetInstallName()
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
990 if not additional_settings:
991 additional_settings = {}
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)
999 for k in additional_settings:
1000 additional_settings[k] = _NormalizeEnvVarReferences(additional_settings[k])
1002 return additional_settings
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}.
1010 str = re.sub(r'\$([a-zA-Z_][a-zA-Z0-9_]*)', r'${\1}', str)
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 + '}')
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)
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].
1039 Throws an Exception in case of dependency cycles.
1041 # Since environment variables can refer to other variables, the evaluation
1042 # order is important. Below is the logic to compute the dependency graph
1044 regex = re.compile(r'\$\{([a-zA-Z0-9\-_]+)\}')
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
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
1060 order = gyp.common.TopologicallySorted(env.keys(), GetEdges)
1063 except gyp.common.CycleError, e:
1065 'Xcode environment variables are cyclically dependent: ' + str(e.nodes))
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)]
1075 def GetSpecPostbuildCommands(spec, quiet=False):
1076 """Returns the list of postbuilds explicitly defined on |spec|, in a form
1077 executable by a shell."""
1079 for postbuild in spec.get('postbuilds', []):
1081 postbuilds.append('echo POSTBUILD\\(%s\\) %s' % (
1082 spec['target_name'], postbuild['postbuild_name']))
1083 postbuilds.append(gyp.common.EncodePOSIXShellList(postbuild['action']))