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.
19 from gyp.common import GypError
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
24 XCODE_VERSION_CACHE = []
26 class XcodeSettings(object):
27 """A class that understands the gyp 'xcode_settings' object."""
29 # Populated lazily by _SdkPath(). Shared by all XcodeSettings, so cached
30 # at class-level for efficiency.
34 # Populated lazily by GetExtraPlistItems(). Shared by all XcodeSettings, so
35 # cached at class-level for efficiency.
38 # Populated lazily by GetIOSPostbuilds. Shared by all XcodeSettings, so
39 # cached at class-level for efficiency.
40 _codesigning_key_cache = {}
42 def __init__(self, spec):
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',
60 # This is only non-None temporarily during the execution of some methods.
61 self.configname = None
63 # Used by _AdjustLibrary to match .a and .dylib entries in libraries.
64 self.library_re = re.compile(r'^lib([^/]+)\.(a|dylib)$')
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]
79 print 'Warning: Conditional keys not implemented, ignoring:', \
80 ' '.join(conditional_keys)
84 assert self.configname
85 return self.xcode_settings[self.configname]
87 def _Test(self, test_key, cond_key, default):
88 return self._Settings().get(test_key, default) == cond_key
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]))
94 lst.append(format_str % str(default))
96 def _WarnUnimplemented(self, test_key):
97 if test_key in self._Settings():
98 print 'Warning: Ignoring not yet implemented key "%s".' % test_key
101 return int(self.spec.get('mac_bundle', 0)) != 0
103 def GetFrameworkVersion(self):
104 """Returns the framework version of the current target. Only valid for
106 assert self._IsBundle()
107 return self.GetPerTargetSetting('FRAMEWORK_VERSION', default='A')
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',
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')
124 assert False, "Don't know extension for '%s', target '%s'" % (
125 self.spec['type'], self.spec['target_name'])
127 def GetProductName(self):
128 """Returns PRODUCT_NAME."""
129 return self.spec.get('product_name', self.spec['target_name'])
131 def GetFullProductName(self):
132 """Returns FULL_PRODUCT_NAME."""
134 return self.GetWrapperName()
136 return self._GetStandaloneBinaryPath()
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()
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."""
148 return self.GetWrapperName()
149 assert self._IsBundle()
150 if self.spec['type'] == 'shared_library':
152 self.GetWrapperName(), 'Versions', self.GetFrameworkVersion())
154 # loadable_modules have a 'Contents' folder like executables.
155 return os.path.join(self.GetWrapperName(), 'Contents')
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()
162 return self.GetBundleContentsFolderPath()
163 return os.path.join(self.GetBundleContentsFolderPath(), 'Resources')
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')
172 return os.path.join(self.GetBundleContentsFolderPath(),
173 'Resources', 'Info.plist')
175 def GetProductType(self):
176 """Returns the PRODUCT_TYPE of this target."""
179 'executable': 'com.apple.product-type.application',
180 'loadable_module': 'com.apple.product-type.bundle',
181 'shared_library': 'com.apple.product-type.framework',
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',
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':
197 'executable': 'mh_execute',
198 'static_library': 'staticlib',
199 'shared_library': 'mh_dylib',
200 'loadable_module': 'mh_bundle',
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())
213 def _GetStandaloneExecutableSuffix(self):
214 if 'product_extension' in self.spec:
215 return '.' + self.spec['product_extension']
218 'static_library': '.a',
219 'shared_library': '.dylib',
220 'loadable_module': '.so',
223 def _GetStandaloneExecutablePrefix(self):
224 return self.spec.get('product_prefix', {
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']])
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':
244 elif self.spec['type'] in ('loadable_module', 'shared_library'):
245 if target[:3] == 'lib':
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
253 def GetExecutableName(self):
254 """Returns the executable name of the bundle represented by this target.
257 return self.spec.get('product_name', self.spec['target_name'])
259 return self._GetStandaloneBinaryPath()
261 def GetExecutablePath(self):
262 """Returns the directory name of the bundle represented by this target. E.g.
263 Chromium.app/Contents/MacOS/Chromium."""
265 return self._GetBundleBinaryPath()
267 return self._GetStandaloneBinaryPath()
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()])
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.
281 return GetStdout(['xcodebuild', '-version', '-sdk', sdk, infoitem])
285 def _SdkRoot(self, configname):
286 if configname is None:
287 configname = self.configname
288 return self.GetPerConfigSetting('SDKROOT', configname, default='')
290 def _SdkPath(self, configname=None):
291 sdk_root = self._SdkRoot(configname)
292 if sdk_root.startswith('/'):
294 return self._XcodeSdkPath(sdk_root)
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
301 XcodeSettings._sdk_root_cache[sdk_path] = sdk_root
302 return XcodeSettings._sdk_path_cache[sdk_root]
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')
313 self._Appendf(lst, 'IPHONEOS_DEPLOYMENT_TARGET',
314 '-miphoneos-version-min=%s')
316 def GetCflags(self, configname, arch=None):
317 """Returns flags that need to be added to .c, .cc, .m, and .mm
319 # This functions (and the similar ones below) do not offer complete
320 # emulation of all xcode_settings keys. They're implemented on demand.
322 self.configname = configname
325 sdk_root = self._SdkPath()
326 if 'SDKROOT' in self._Settings() and sdk_root:
327 cflags.append('-isysroot %s' % sdk_root)
329 if self._Test('CLANG_WARN_CONSTANT_CONVERSION', 'YES', default='NO'):
330 cflags.append('-Wconstant-conversion')
332 if self._Test('GCC_CHAR_IS_UNSIGNED_CHAR', 'YES', default='NO'):
333 cflags.append('-funsigned-char')
335 if self._Test('GCC_CW_ASM_SYNTAX', 'YES', default='YES'):
336 cflags.append('-fasm-blocks')
338 if 'GCC_DYNAMIC_NO_PIC' in self._Settings():
339 if self._Settings()['GCC_DYNAMIC_NO_PIC'] == 'YES':
340 cflags.append('-mdynamic-no-pic')
343 # TODO: In this case, it depends on the target. xcode passes
344 # mdynamic-no-pic by default for executable and possibly static lib
347 if self._Test('GCC_ENABLE_PASCAL_STRINGS', 'YES', default='YES'):
348 cflags.append('-mpascal-strings')
350 self._Appendf(cflags, 'GCC_OPTIMIZATION_LEVEL', '-O%s', default='s')
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')
361 raise NotImplementedError('Unknown debug format %s' % dbg_format)
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')
368 if self._Test('GCC_SYMBOLS_PRIVATE_EXTERN', 'YES', default='NO'):
369 cflags.append('-fvisibility=hidden')
371 if self._Test('GCC_TREAT_WARNINGS_AS_ERRORS', 'YES', default='NO'):
372 cflags.append('-Werror')
374 if self._Test('GCC_WARN_ABOUT_MISSING_NEWLINE', 'YES', default='NO'):
375 cflags.append('-Wnewline-eof')
377 self._AppendPlatformVersionMinFlags(cflags)
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')
385 # TODO: This is exported correctly, but assigning to it is not supported.
386 self._WarnUnimplemented('MACH_O_TYPE')
387 self._WarnUnimplemented('PRODUCT_TYPE')
392 archs = self._Settings().get('ARCHS', [self._DefaultArch()])
394 # TODO: Supporting fat binaries will be annoying.
395 self._WarnUnimplemented('ARCHS')
397 cflags.append('-arch ' + archs[0])
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',
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')
410 cflags += self._Settings().get('WARNING_CFLAGS', [])
413 framework_root = sdk_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))
421 self.configname = None
424 def GetCflagsC(self, configname):
425 """Returns flags that need to be added to .c, and .m compilations."""
426 self.configname = configname
428 if self._Settings().get('GCC_C_LANGUAGE_STANDARD', '') == 'ansi':
429 cflags_c.append('-ansi')
431 self._Appendf(cflags_c, 'GCC_C_LANGUAGE_STANDARD', '-std=%s')
432 cflags_c += self._Settings().get('OTHER_CFLAGS', [])
433 self.configname = None
436 def GetCflagsCC(self, configname):
437 """Returns flags that need to be added to .cc, and .mm compilations."""
438 self.configname = configname
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)
448 self._Appendf(cflags_cc, 'CLANG_CXX_LIBRARY', '-stdlib=%s')
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')
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', [])
471 other_ccflags.append(flag)
472 cflags_cc += other_ccflags
474 self.configname = None
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')
484 def _AddObjectiveCARCFlags(self, flags):
485 if self._Test('CLANG_ENABLE_OBJC_ARC', 'YES', default='NO'):
486 flags.append('-fobjc-arc')
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')
493 def GetCflagsObjC(self, configname):
494 """Returns flags that need to be added to .m compilations."""
495 self.configname = configname
497 self._AddObjectiveCGarbageCollectionFlags(cflags_objc)
498 self._AddObjectiveCARCFlags(cflags_objc)
499 self._AddObjectiveCMissingPropertySynthesisFlags(cflags_objc)
500 self.configname = None
503 def GetCflagsObjCC(self, configname):
504 """Returns flags that need to be added to .mm compilations."""
505 self.configname = configname
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
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())):
521 install_base = self.GetPerTargetSetting(
522 'DYLIB_INSTALL_NAME_BASE',
523 default='/Library/Frameworks' if self._IsBundle() else '/usr/local/lib')
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.
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)
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())):
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)
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))
559 install_name = install_name.replace(
560 '$(DYLIB_INSTALL_NAME_BASE:standardizepath)',
561 self._StandardizePath(self.GetInstallNameBase()))
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())
569 assert '$(WRAPPER_NAME)' not in install_name
570 assert '$(PRODUCT_NAME)' not in install_name
572 install_name = install_name.replace(
573 '$(EXECUTABLE_PATH)', self.GetExecutablePath())
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+)'
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.
592 for flag_pattern in linker_flags:
593 regex = re.compile('(?:-Wl,)?' + '[ ,]'.join(flag_pattern))
594 m = regex.match(ldflag)
596 ldflag = ldflag[:m.start(1)] + gyp_to_build_path(m.group(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'):])
604 def GetLdflags(self, configname, product_dir, gyp_to_build_path, arch=None):
605 """Returns flags that need to be passed to the linker.
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.
614 self.configname = configname
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))
622 if self._Test('DEAD_CODE_STRIPPING', 'YES', default='NO'):
623 ldflags.append('-Wl,-dead_strip')
625 if self._Test('PREBINDING', 'YES', default='NO'):
626 ldflags.append('-Wl,-prebind')
629 ldflags, 'DYLIB_COMPATIBILITY_VERSION', '-compatibility_version %s')
631 ldflags, 'DYLIB_CURRENT_VERSION', '-current_version %s')
633 self._AppendPlatformVersionMinFlags(ldflags)
635 if 'SDKROOT' in self._Settings() and self._SdkPath():
636 ldflags.append('-isysroot ' + self._SdkPath())
638 for library_path in self._Settings().get('LIBRARY_SEARCH_PATHS', []):
639 ldflags.append('-L' + gyp_to_build_path(library_path))
641 if 'ORDER_FILE' in self._Settings():
642 ldflags.append('-Wl,-order_file ' +
643 '-Wl,' + gyp_to_build_path(
644 self._Settings()['ORDER_FILE']))
649 archs = self._Settings().get('ARCHS', [self._DefaultArch()])
651 # TODO: Supporting fat binaries will be annoying.
652 self._WarnUnimplemented('ARCHS')
654 ldflags.append('-arch ' + archs[0])
656 # Xcode adds the product directory by default.
657 ldflags.append('-L' + product_dir)
659 install_name = self.GetInstallName()
660 if install_name and self.spec['type'] != 'loadable_module':
661 ldflags.append('-install_name ' + install_name.replace(' ', r'\ '))
663 for rpath in self._Settings().get('LD_RUNPATH_SEARCH_PATHS', []):
664 ldflags.append('-Wl,-rpath,' + rpath)
666 sdk_root = self._SdkPath()
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))
674 self.configname = None
677 def GetLibtoolflags(self, configname):
678 """Returns flags that need to be passed to the static linker.
681 configname: The name of the configuration to get ld flags for.
683 self.configname = configname
686 for libtoolflag in self._Settings().get('OTHER_LDFLAGS', []):
687 libtoolflags.append(libtoolflag)
688 # TODO(thakis): ARCHS?
690 self.configname = None
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."""
698 for configname in sorted(self.xcode_settings.keys()):
700 result = dict(self.xcode_settings[configname])
703 for key, value in self.xcode_settings[configname].iteritems():
704 if key not in result:
706 elif result[key] != value:
710 def GetPerConfigSetting(self, setting, configname, default=None):
711 if configname in self.xcode_settings:
712 return self.xcode_settings[configname].get(setting, default)
714 return self.GetPerTargetSetting(setting, default)
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."""
721 for configname in sorted(self.xcode_settings.keys()):
723 result = self.xcode_settings[configname].get(setting, None)
724 is_first_pass = False
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']))
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
740 if (self._Test('DEPLOYMENT_POSTPROCESSING', 'YES', default='NO') and
741 self._Test('STRIP_INSTALLED_PRODUCT', 'YES', default='NO')):
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'
749 strip_style = self._Settings().get('STRIP_STYLE', default_strip_style)
756 explicit_strip_flags = self._Settings().get('STRIPFLAGS', '')
757 if explicit_strip_flags:
758 strip_flags += ' ' + _NormalizeEnvVarReferences(explicit_strip_flags)
761 result.append('echo STRIP\\(%s\\)' % self.spec['target_name'])
762 result.append('strip %s %s' % (strip_flags, output_binary))
764 self.configname = None
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
773 # For static libraries, no dSYMs are created.
775 if (self._Test('GCC_GENERATE_DEBUGGING_SYMBOLS', 'YES', default='YES') and
777 'DEBUG_INFORMATION_FORMAT', 'dwarf-with-dsym', default='dwarf') and
778 self.spec['type'] != 'static_library'):
780 result.append('echo DSYMUTIL\\(%s\\)' % self.spec['target_name'])
781 result.append('dsymutil %s -o %s' % (output_binary, output + '.dSYM'))
783 self.configname = None
786 def _GetTargetPostbuilds(self, configname, output, output_binary,
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.
792 self._GetDebugInfoPostbuilds(configname, output, output_binary, quiet) +
793 self._GetStripPostbuilds(configname, output_binary, quiet))
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
799 if not (self.isIOS and self.spec['type'] == "executable"):
802 settings = self.xcode_settings[configname]
803 key = self._GetIOSCodeSignIdentityKey(settings)
807 # Warn for any unimplemented signing xcode keys.
808 unimpl = ['OTHER_CODE_SIGN_FLAGS']
809 unimpl = set(unimpl) & set(self.xcode_settings[configname].keys())
811 print 'Warning: Some codesign keys not implemented, ignoring: %s' % (
812 ', '.join(sorted(unimpl)))
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', ''))
821 def _GetIOSCodeSignIdentityKey(self, settings):
822 identity = settings.get('CODE_SIGN_IDENTITY')
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():
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, '')
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
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
846 def _AdjustLibrary(self, library, config_name=None):
847 if library.endswith('.framework'):
848 l = '-framework ' + os.path.splitext(os.path.basename(library))[0]
850 m = self.library_re.match(library)
852 l = '-l' + m.group(1)
856 sdk_root = self._SdkPath(config_name)
859 return l.replace('$(SDKROOT)', sdk_root)
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.
865 libraries = [self._AdjustLibrary(library, config_name)
866 for library in libraries]
869 def _BuildMachineOSBuild(self):
870 return GetStdout(['sw_vers', '-buildVersion'])
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(',')]
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:
880 cache['BuildMachineOSBuild'] = self._BuildMachineOSBuild()
882 xcode, xcode_build = XcodeVersion()
883 cache['DTXcode'] = xcode
884 cache['DTXcodeBuild'] = xcode_build
886 sdk_root = self._SdkRoot(configname)
888 sdk_root = self._DefaultSdkRoot()
889 cache['DTSDKName'] = sdk_root
891 cache['DTSDKBuild'] = self._GetSdkVersionInfoItem(
892 sdk_root, 'ProductBuildVersion')
894 cache['DTSDKBuild'] = cache['BuildMachineOSBuild']
897 cache['DTPlatformName'] = cache['DTSDKName']
898 if configname.endswith("iphoneos"):
899 cache['DTPlatformVersion'] = self._GetSdkVersionInfoItem(
900 sdk_root, 'ProductVersion')
901 cache['CFBundleSupportedPlatforms'] = ['iPhoneOS']
903 cache['CFBundleSupportedPlatforms'] = ['iPhoneSimulator']
904 XcodeSettings._plist_cache[configname] = cache
906 # Include extra plist items that are per-target, not per global
908 items = dict(XcodeSettings._plist_cache[configname])
910 items['UIDeviceFamily'] = self._XcodeIOSDeviceFamily(configname)
913 def _DefaultSdkRoot(self):
914 """Returns the default SDKROOT to use.
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.
920 xcode_version, xcode_build = XcodeVersion()
921 if xcode_version < '0500':
923 default_sdk_path = self._XcodeSdkPath('')
924 default_sdk_root = XcodeSettings._sdk_root_cache.get(default_sdk_path)
926 return default_sdk_root
928 all_sdks = GetStdout(['xcodebuild', '-showsdks'])
930 # If xcodebuild fails, there will be no valid SDKs
932 for line in all_sdks.splitlines():
934 if len(items) >= 3 and items[-2] == '-sdk':
936 sdk_path = self._XcodeSdkPath(sdk_root)
937 if sdk_path == default_sdk_path:
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".
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
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).
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.
959 version, build = XcodeVersion()
960 if version >= '0500':
964 class MacPrefixHeader(object):
965 """A class that helps with emulating Xcode's GCC_PREFIX_HEADER feature.
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.
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.
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.
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').
998 # This doesn't support per-configuration prefix headers. Good enough
1001 self.compile_headers = False
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 = {}
1008 if self.compile_headers:
1009 for lang in ['c', 'cc', 'm', 'mm']:
1010 self.compiled_headers[lang] = gyp_path_to_build_output(
1012 self.header = gyp_path_to_build_path(self.header)
1014 def _CompiledHeader(self, lang, arch):
1015 assert self.compile_headers
1016 h = self.compiled_headers[lang]
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)
1026 return '-include %s' % self.header
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'
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:
1044 for source, obj in zip(sources, objs):
1045 ext = os.path.splitext(source)[1]
1048 '.cpp': 'cc', '.cc': 'cc', '.cxx': 'cc',
1053 result.append((source, obj, self._Gch(lang, arch)))
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.
1060 if not self.header or not self.compile_headers:
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),
1071 """Returns a tuple of version and build version of installed Xcode."""
1072 # `xcodebuild -version` output looks like
1074 # Build version 4H1503
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])
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"
1093 version = CLTVersion()
1095 version = re.match('(\d\.\d\.?\d*)', version).groups()[0]
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)
1106 build = build.split()[-1]
1107 XCODE_VERSION_CACHE.extend((version, build))
1108 return version, build
1111 # This function ported from the logic in Homebrew's CLT version check
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
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"
1125 regex = re.compile('version: (?P<version>.+)')
1126 for key in [MAVERICKS_PKG_ID, STANDALONE_PKG_ID, FROM_XCODE_PKG_ID]:
1128 output = GetStdout(['/usr/sbin/pkgutil', '--pkg-info', key])
1129 return re.search(regex, output).groupdict()['version']
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')
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.
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
1161 def IsMacBundle(flavor, spec):
1162 """Returns if |spec| should be treated as a bundle.
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')
1169 assert spec['type'] != 'none', (
1170 'mac_bundle targets cannot have type none (target "%s")' %
1171 spec['target_name'])
1172 return is_mac_bundle
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.
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.
1185 dest = os.path.join(product_dir,
1186 xcode_settings.GetBundleResourceFolder())
1187 for res in resources:
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)
1195 # Split into (path,file).
1196 res_parts = os.path.split(res)
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])
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'
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
1219 * |dest_plist| is the destination plist path, relative to the
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|.
1226 Only call this for mac bundle targets.
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.
1235 info_plist = xcode_settings.GetPerTargetSetting('INFOPLIST_FILE')
1237 return None, None, [], {}
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)
1244 info_plist = gyp_path_to_build_path(info_plist)
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=''))
1256 dest_plist = os.path.join(product_dir, xcode_settings.GetBundlePlistPath())
1257 extra_env = xcode_settings.GetPerTargetSettings()
1259 return info_plist, dest_plist, defines, extra_env
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
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
1277 if not xcode_settings: return {}
1279 # This function is considered a friend of XcodeSettings, so let it reach into
1280 # its implementation details.
1281 spec = xcode_settings.spec
1283 # These are filled in on a as-needed basis.
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}',
1296 if xcode_settings.GetPerConfigSetting('SDKROOT', configuration):
1297 env['SDKROOT'] = xcode_settings._SdkPath(configuration)
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()
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()
1318 install_name = xcode_settings.GetInstallName()
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
1325 if not additional_settings:
1326 additional_settings = {}
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)
1334 for k in additional_settings:
1335 additional_settings[k] = _NormalizeEnvVarReferences(additional_settings[k])
1337 return additional_settings
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}.
1345 str = re.sub(r'\$([a-zA-Z_][a-zA-Z0-9_]*)', r'${\1}', str)
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 + '}')
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)
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].
1374 Throws an Exception in case of dependency cycles.
1376 # Since environment variables can refer to other variables, the evaluation
1377 # order is important. Below is the logic to compute the dependency graph
1379 regex = re.compile(r'\$\{([a-zA-Z0-9\-_]+)\}')
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
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
1395 order = gyp.common.TopologicallySorted(env.keys(), GetEdges)
1398 except gyp.common.CycleError, e:
1400 'Xcode environment variables are cyclically dependent: ' + str(e.nodes))
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)]
1410 def GetSpecPostbuildCommands(spec, quiet=False):
1411 """Returns the list of postbuilds explicitly defined on |spec|, in a form
1412 executable by a shell."""
1414 for postbuild in spec.get('postbuilds', []):
1416 postbuilds.append('echo POSTBUILD\\(%s\\) %s' % (
1417 spec['target_name'], postbuild['postbuild_name']))
1418 postbuilds.append(gyp.common.EncodePOSIXShellList(postbuild['action']))
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'):
1432 def _IOSIsDeviceSDKROOT(sdkroot):
1433 """Tests if |sdkroot| is a SDK for building for device."""
1434 return 'iphoneos' in sdkroot.lower()
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
1441 xcode_version, xcode_build = XcodeVersion()
1442 if xcode_version < '0500':
1443 if _IOSIsDeviceSDKROOT(sdkroot):
1444 return {'$(ARCHS_STANDARD)': ['armv7']}
1446 return {'$(ARCHS_STANDARD)': ['i386']}
1448 if _IOSIsDeviceSDKROOT(sdkroot):
1450 '$(ARCHS_STANDARD)': ['armv7', 'armv7s'],
1451 '$(ARCHS_STANDARD_INCLUDING_64_BIT)': ['armv7', 'armv7s', 'arm64'],
1455 '$(ARCHS_STANDARD)': ['i386'],
1456 '$(ARCHS_STANDARD_INCLUDING_64_BIT)': ['i386', 'x86_64'],
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', []))
1475 selected_archs = selected_archs & valid_archs
1476 xcode_settings['ARCHS'] = list(selected_archs)
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'])
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)