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.
17 from gyp.common import GypError
19 class XcodeSettings(object):
20 """A class that understands the gyp 'xcode_settings' object."""
22 # Populated lazily by _SdkPath(). Shared by all XcodeSettings, so cached
23 # at class-level for efficiency.
26 # Populated lazily by GetExtraPlistItems(). Shared by all XcodeSettings, so
27 # cached at class-level for efficiency.
30 # Populated lazily by GetIOSPostbuilds. Shared by all XcodeSettings, so
31 # cached at class-level for efficiency.
32 _codesigning_key_cache = {}
34 def __init__(self, spec):
39 # Per-target 'xcode_settings' are pushed down into configs earlier by gyp.
40 # This means self.xcode_settings[config] always contains all settings
41 # for that config -- the per-target settings as well. Settings that are
42 # the same for all configs are implicitly per-target settings.
43 self.xcode_settings = {}
44 configs = spec['configurations']
45 for configname, config in configs.iteritems():
46 self.xcode_settings[configname] = config.get('xcode_settings', {})
47 self._ConvertConditionalKeys(configname)
48 if self.xcode_settings[configname].get('IPHONEOS_DEPLOYMENT_TARGET',
52 # This is only non-None temporarily during the execution of some methods.
53 self.configname = None
55 # Used by _AdjustLibrary to match .a and .dylib entries in libraries.
56 self.library_re = re.compile(r'^lib([^/]+)\.(a|dylib)$')
58 def _ConvertConditionalKeys(self, configname):
59 """Converts or warns on conditional keys. Xcode supports conditional keys,
60 such as CODE_SIGN_IDENTITY[sdk=iphoneos*]. This is a partial implementation
61 with some keys converted while the rest force a warning."""
62 settings = self.xcode_settings[configname]
63 conditional_keys = [key for key in settings if key.endswith(']')]
64 for key in conditional_keys:
65 # If you need more, speak up at http://crbug.com/122592
66 if key.endswith("[sdk=iphoneos*]"):
67 if configname.endswith("iphoneos"):
68 new_key = key.split("[")[0]
69 settings[new_key] = settings[key]
71 print 'Warning: Conditional keys not implemented, ignoring:', \
72 ' '.join(conditional_keys)
76 assert self.configname
77 return self.xcode_settings[self.configname]
79 def _Test(self, test_key, cond_key, default):
80 return self._Settings().get(test_key, default) == cond_key
82 def _Appendf(self, lst, test_key, format_str, default=None):
83 if test_key in self._Settings():
84 lst.append(format_str % str(self._Settings()[test_key]))
86 lst.append(format_str % str(default))
88 def _WarnUnimplemented(self, test_key):
89 if test_key in self._Settings():
90 print 'Warning: Ignoring not yet implemented key "%s".' % test_key
93 return int(self.spec.get('mac_bundle', 0)) != 0
95 def GetFrameworkVersion(self):
96 """Returns the framework version of the current target. Only valid for
98 assert self._IsBundle()
99 return self.GetPerTargetSetting('FRAMEWORK_VERSION', default='A')
101 def GetWrapperExtension(self):
102 """Returns the bundle extension (.app, .framework, .plugin, etc). Only
103 valid for bundles."""
104 assert self._IsBundle()
105 if self.spec['type'] in ('loadable_module', 'shared_library'):
106 default_wrapper_extension = {
107 'loadable_module': 'bundle',
108 'shared_library': 'framework',
110 wrapper_extension = self.GetPerTargetSetting(
111 'WRAPPER_EXTENSION', default=default_wrapper_extension)
112 return '.' + self.spec.get('product_extension', wrapper_extension)
113 elif self.spec['type'] == 'executable':
114 return '.' + self.spec.get('product_extension', 'app')
116 assert False, "Don't know extension for '%s', target '%s'" % (
117 self.spec['type'], self.spec['target_name'])
119 def GetProductName(self):
120 """Returns PRODUCT_NAME."""
121 return self.spec.get('product_name', self.spec['target_name'])
123 def GetFullProductName(self):
124 """Returns FULL_PRODUCT_NAME."""
126 return self.GetWrapperName()
128 return self._GetStandaloneBinaryPath()
130 def GetWrapperName(self):
131 """Returns the directory name of the bundle represented by this target.
132 Only valid for bundles."""
133 assert self._IsBundle()
134 return self.GetProductName() + self.GetWrapperExtension()
136 def GetBundleContentsFolderPath(self):
137 """Returns the qualified path to the bundle's contents folder. E.g.
138 Chromium.app/Contents or Foo.bundle/Versions/A. Only valid for bundles."""
140 return self.GetWrapperName()
141 assert self._IsBundle()
142 if self.spec['type'] == 'shared_library':
144 self.GetWrapperName(), 'Versions', self.GetFrameworkVersion())
146 # loadable_modules have a 'Contents' folder like executables.
147 return os.path.join(self.GetWrapperName(), 'Contents')
149 def GetBundleResourceFolder(self):
150 """Returns the qualified path to the bundle's resource folder. E.g.
151 Chromium.app/Contents/Resources. Only valid for bundles."""
152 assert self._IsBundle()
154 return self.GetBundleContentsFolderPath()
155 return os.path.join(self.GetBundleContentsFolderPath(), 'Resources')
157 def GetBundlePlistPath(self):
158 """Returns the qualified path to the bundle's plist file. E.g.
159 Chromium.app/Contents/Info.plist. Only valid for bundles."""
160 assert self._IsBundle()
161 if self.spec['type'] in ('executable', 'loadable_module'):
162 return os.path.join(self.GetBundleContentsFolderPath(), 'Info.plist')
164 return os.path.join(self.GetBundleContentsFolderPath(),
165 'Resources', 'Info.plist')
167 def GetProductType(self):
168 """Returns the PRODUCT_TYPE of this target."""
171 'executable': 'com.apple.product-type.application',
172 'loadable_module': 'com.apple.product-type.bundle',
173 'shared_library': 'com.apple.product-type.framework',
177 'executable': 'com.apple.product-type.tool',
178 'loadable_module': 'com.apple.product-type.library.dynamic',
179 'shared_library': 'com.apple.product-type.library.dynamic',
180 'static_library': 'com.apple.product-type.library.static',
183 def GetMachOType(self):
184 """Returns the MACH_O_TYPE of this target."""
185 # Weird, but matches Xcode.
186 if not self._IsBundle() and self.spec['type'] == 'executable':
189 'executable': 'mh_execute',
190 'static_library': 'staticlib',
191 'shared_library': 'mh_dylib',
192 'loadable_module': 'mh_bundle',
195 def _GetBundleBinaryPath(self):
196 """Returns the name of the bundle binary of by this target.
197 E.g. Chromium.app/Contents/MacOS/Chromium. Only valid for bundles."""
198 assert self._IsBundle()
199 if self.spec['type'] in ('shared_library') or self.isIOS:
200 path = self.GetBundleContentsFolderPath()
201 elif self.spec['type'] in ('executable', 'loadable_module'):
202 path = os.path.join(self.GetBundleContentsFolderPath(), 'MacOS')
203 return os.path.join(path, self.GetExecutableName())
205 def _GetStandaloneExecutableSuffix(self):
206 if 'product_extension' in self.spec:
207 return '.' + self.spec['product_extension']
210 'static_library': '.a',
211 'shared_library': '.dylib',
212 'loadable_module': '.so',
215 def _GetStandaloneExecutablePrefix(self):
216 return self.spec.get('product_prefix', {
218 'static_library': 'lib',
219 'shared_library': 'lib',
220 # Non-bundled loadable_modules are called foo.so for some reason
221 # (that is, .so and no prefix) with the xcode build -- match that.
222 'loadable_module': '',
223 }[self.spec['type']])
225 def _GetStandaloneBinaryPath(self):
226 """Returns the name of the non-bundle binary represented by this target.
227 E.g. hello_world. Only valid for non-bundles."""
228 assert not self._IsBundle()
229 assert self.spec['type'] in (
230 'executable', 'shared_library', 'static_library', 'loadable_module'), (
231 'Unexpected type %s' % self.spec['type'])
232 target = self.spec['target_name']
233 if self.spec['type'] == 'static_library':
234 if target[:3] == 'lib':
236 elif self.spec['type'] in ('loadable_module', 'shared_library'):
237 if target[:3] == 'lib':
240 target_prefix = self._GetStandaloneExecutablePrefix()
241 target = self.spec.get('product_name', target)
242 target_ext = self._GetStandaloneExecutableSuffix()
243 return target_prefix + target + target_ext
245 def GetExecutableName(self):
246 """Returns the executable name of the bundle represented by this target.
249 return self.spec.get('product_name', self.spec['target_name'])
251 return self._GetStandaloneBinaryPath()
253 def GetExecutablePath(self):
254 """Returns the directory name of the bundle represented by this target. E.g.
255 Chromium.app/Contents/MacOS/Chromium."""
257 return self._GetBundleBinaryPath()
259 return self._GetStandaloneBinaryPath()
261 def GetActiveArchs(self, configname):
262 """Returns the architectures this target should be built for."""
263 # TODO: Look at VALID_ARCHS, ONLY_ACTIVE_ARCH; possibly set
264 # CURRENT_ARCH / NATIVE_ARCH env vars?
265 return self.xcode_settings[configname].get('ARCHS', ['i386'])
267 def _GetStdout(self, cmdlist):
268 job = subprocess.Popen(cmdlist, stdout=subprocess.PIPE)
269 out = job.communicate()[0]
270 if job.returncode != 0:
271 sys.stderr.write(out + '\n')
272 raise GypError('Error %d running %s' % (job.returncode, cmdlist[0]))
273 return out.rstrip('\n')
275 def _GetSdkVersionInfoItem(self, sdk, infoitem):
276 return self._GetStdout(['xcodebuild', '-version', '-sdk', sdk, infoitem])
278 def _SdkRoot(self, configname):
279 if configname is None:
280 configname = self.configname
281 return self.GetPerConfigSetting('SDKROOT', configname, default='')
283 def _SdkPath(self, configname=None):
284 sdk_root = self._SdkRoot(configname)
285 if sdk_root.startswith('/'):
287 if sdk_root not in XcodeSettings._sdk_path_cache:
288 XcodeSettings._sdk_path_cache[sdk_root] = self._GetSdkVersionInfoItem(
290 return XcodeSettings._sdk_path_cache[sdk_root]
292 def _AppendPlatformVersionMinFlags(self, lst):
293 self._Appendf(lst, 'MACOSX_DEPLOYMENT_TARGET', '-mmacosx-version-min=%s')
294 if 'IPHONEOS_DEPLOYMENT_TARGET' in self._Settings():
295 # TODO: Implement this better?
296 sdk_path_basename = os.path.basename(self._SdkPath())
297 if sdk_path_basename.lower().startswith('iphonesimulator'):
298 self._Appendf(lst, 'IPHONEOS_DEPLOYMENT_TARGET',
299 '-mios-simulator-version-min=%s')
301 self._Appendf(lst, 'IPHONEOS_DEPLOYMENT_TARGET',
302 '-miphoneos-version-min=%s')
304 def GetCflags(self, configname, arch=None):
305 """Returns flags that need to be added to .c, .cc, .m, and .mm
307 # This functions (and the similar ones below) do not offer complete
308 # emulation of all xcode_settings keys. They're implemented on demand.
310 self.configname = configname
313 sdk_root = self._SdkPath()
314 if 'SDKROOT' in self._Settings():
315 cflags.append('-isysroot %s' % sdk_root)
317 if self._Test('CLANG_WARN_CONSTANT_CONVERSION', 'YES', default='NO'):
318 cflags.append('-Wconstant-conversion')
320 if self._Test('GCC_CHAR_IS_UNSIGNED_CHAR', 'YES', default='NO'):
321 cflags.append('-funsigned-char')
323 if self._Test('GCC_CW_ASM_SYNTAX', 'YES', default='YES'):
324 cflags.append('-fasm-blocks')
326 if 'GCC_DYNAMIC_NO_PIC' in self._Settings():
327 if self._Settings()['GCC_DYNAMIC_NO_PIC'] == 'YES':
328 cflags.append('-mdynamic-no-pic')
331 # TODO: In this case, it depends on the target. xcode passes
332 # mdynamic-no-pic by default for executable and possibly static lib
335 if self._Test('GCC_ENABLE_PASCAL_STRINGS', 'YES', default='YES'):
336 cflags.append('-mpascal-strings')
338 self._Appendf(cflags, 'GCC_OPTIMIZATION_LEVEL', '-O%s', default='s')
340 if self._Test('GCC_GENERATE_DEBUGGING_SYMBOLS', 'YES', default='YES'):
341 dbg_format = self._Settings().get('DEBUG_INFORMATION_FORMAT', 'dwarf')
342 if dbg_format == 'dwarf':
343 cflags.append('-gdwarf-2')
344 elif dbg_format == 'stabs':
345 raise NotImplementedError('stabs debug format is not supported yet.')
346 elif dbg_format == 'dwarf-with-dsym':
347 cflags.append('-gdwarf-2')
349 raise NotImplementedError('Unknown debug format %s' % dbg_format)
351 if self._Settings().get('GCC_STRICT_ALIASING') == 'YES':
352 cflags.append('-fstrict-aliasing')
353 elif self._Settings().get('GCC_STRICT_ALIASING') == 'NO':
354 cflags.append('-fno-strict-aliasing')
356 if self._Test('GCC_SYMBOLS_PRIVATE_EXTERN', 'YES', default='NO'):
357 cflags.append('-fvisibility=hidden')
359 if self._Test('GCC_TREAT_WARNINGS_AS_ERRORS', 'YES', default='NO'):
360 cflags.append('-Werror')
362 if self._Test('GCC_WARN_ABOUT_MISSING_NEWLINE', 'YES', default='NO'):
363 cflags.append('-Wnewline-eof')
365 self._AppendPlatformVersionMinFlags(cflags)
368 if self._Test('COPY_PHASE_STRIP', 'YES', default='NO'):
369 self._WarnUnimplemented('COPY_PHASE_STRIP')
370 self._WarnUnimplemented('GCC_DEBUGGING_SYMBOLS')
371 self._WarnUnimplemented('GCC_ENABLE_OBJC_EXCEPTIONS')
373 # TODO: This is exported correctly, but assigning to it is not supported.
374 self._WarnUnimplemented('MACH_O_TYPE')
375 self._WarnUnimplemented('PRODUCT_TYPE')
380 archs = self._Settings().get('ARCHS', ['i386'])
382 # TODO: Supporting fat binaries will be annoying.
383 self._WarnUnimplemented('ARCHS')
385 cflags.append('-arch ' + archs[0])
387 if archs[0] in ('i386', 'x86_64'):
388 if self._Test('GCC_ENABLE_SSE3_EXTENSIONS', 'YES', default='NO'):
389 cflags.append('-msse3')
390 if self._Test('GCC_ENABLE_SUPPLEMENTAL_SSE3_INSTRUCTIONS', 'YES',
392 cflags.append('-mssse3') # Note 3rd 's'.
393 if self._Test('GCC_ENABLE_SSE41_EXTENSIONS', 'YES', default='NO'):
394 cflags.append('-msse4.1')
395 if self._Test('GCC_ENABLE_SSE42_EXTENSIONS', 'YES', default='NO'):
396 cflags.append('-msse4.2')
398 cflags += self._Settings().get('WARNING_CFLAGS', [])
400 config = self.spec['configurations'][self.configname]
401 framework_dirs = config.get('mac_framework_dirs', [])
402 for directory in framework_dirs:
403 cflags.append('-F' + directory.replace('$(SDKROOT)', sdk_root))
405 self.configname = None
408 def GetCflagsC(self, configname):
409 """Returns flags that need to be added to .c, and .m compilations."""
410 self.configname = configname
412 if self._Settings().get('GCC_C_LANGUAGE_STANDARD', '') == 'ansi':
413 cflags_c.append('-ansi')
415 self._Appendf(cflags_c, 'GCC_C_LANGUAGE_STANDARD', '-std=%s')
416 cflags_c += self._Settings().get('OTHER_CFLAGS', [])
417 self.configname = None
420 def GetCflagsCC(self, configname):
421 """Returns flags that need to be added to .cc, and .mm compilations."""
422 self.configname = configname
425 clang_cxx_language_standard = self._Settings().get(
426 'CLANG_CXX_LANGUAGE_STANDARD')
427 # Note: Don't make c++0x to c++11 so that c++0x can be used with older
428 # clangs that don't understand c++11 yet (like Xcode 4.2's).
429 if clang_cxx_language_standard:
430 cflags_cc.append('-std=%s' % clang_cxx_language_standard)
432 self._Appendf(cflags_cc, 'CLANG_CXX_LIBRARY', '-stdlib=%s')
434 if self._Test('GCC_ENABLE_CPP_RTTI', 'NO', default='YES'):
435 cflags_cc.append('-fno-rtti')
436 if self._Test('GCC_ENABLE_CPP_EXCEPTIONS', 'NO', default='YES'):
437 cflags_cc.append('-fno-exceptions')
438 if self._Test('GCC_INLINES_ARE_PRIVATE_EXTERN', 'YES', default='NO'):
439 cflags_cc.append('-fvisibility-inlines-hidden')
440 if self._Test('GCC_THREADSAFE_STATICS', 'NO', default='YES'):
441 cflags_cc.append('-fno-threadsafe-statics')
442 # Note: This flag is a no-op for clang, it only has an effect for gcc.
443 if self._Test('GCC_WARN_ABOUT_INVALID_OFFSETOF_MACRO', 'NO', default='YES'):
444 cflags_cc.append('-Wno-invalid-offsetof')
448 for flag in self._Settings().get('OTHER_CPLUSPLUSFLAGS', ['$(inherited)']):
449 # TODO: More general variable expansion. Missing in many other places too.
450 if flag in ('$inherited', '$(inherited)', '${inherited}'):
451 flag = '$OTHER_CFLAGS'
452 if flag in ('$OTHER_CFLAGS', '$(OTHER_CFLAGS)', '${OTHER_CFLAGS}'):
453 other_ccflags += self._Settings().get('OTHER_CFLAGS', [])
455 other_ccflags.append(flag)
456 cflags_cc += other_ccflags
458 self.configname = None
461 def _AddObjectiveCGarbageCollectionFlags(self, flags):
462 gc_policy = self._Settings().get('GCC_ENABLE_OBJC_GC', 'unsupported')
463 if gc_policy == 'supported':
464 flags.append('-fobjc-gc')
465 elif gc_policy == 'required':
466 flags.append('-fobjc-gc-only')
468 def _AddObjectiveCARCFlags(self, flags):
469 if self._Test('CLANG_ENABLE_OBJC_ARC', 'YES', default='NO'):
470 flags.append('-fobjc-arc')
472 def _AddObjectiveCMissingPropertySynthesisFlags(self, flags):
473 if self._Test('CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS',
474 'YES', default='NO'):
475 flags.append('-Wobjc-missing-property-synthesis')
477 def GetCflagsObjC(self, configname):
478 """Returns flags that need to be added to .m compilations."""
479 self.configname = configname
481 self._AddObjectiveCGarbageCollectionFlags(cflags_objc)
482 self._AddObjectiveCARCFlags(cflags_objc)
483 self._AddObjectiveCMissingPropertySynthesisFlags(cflags_objc)
484 self.configname = None
487 def GetCflagsObjCC(self, configname):
488 """Returns flags that need to be added to .mm compilations."""
489 self.configname = configname
491 self._AddObjectiveCGarbageCollectionFlags(cflags_objcc)
492 self._AddObjectiveCARCFlags(cflags_objcc)
493 self._AddObjectiveCMissingPropertySynthesisFlags(cflags_objcc)
494 if self._Test('GCC_OBJC_CALL_CXX_CDTORS', 'YES', default='NO'):
495 cflags_objcc.append('-fobjc-call-cxx-cdtors')
496 self.configname = None
499 def GetInstallNameBase(self):
500 """Return DYLIB_INSTALL_NAME_BASE for this target."""
501 # Xcode sets this for shared_libraries, and for nonbundled loadable_modules.
502 if (self.spec['type'] != 'shared_library' and
503 (self.spec['type'] != 'loadable_module' or self._IsBundle())):
505 install_base = self.GetPerTargetSetting(
506 'DYLIB_INSTALL_NAME_BASE',
507 default='/Library/Frameworks' if self._IsBundle() else '/usr/local/lib')
510 def _StandardizePath(self, path):
511 """Do :standardizepath processing for path."""
512 # I'm not quite sure what :standardizepath does. Just call normpath(),
513 # but don't let @executable_path/../foo collapse to foo.
515 prefix, rest = '', path
516 if path.startswith('@'):
517 prefix, rest = path.split('/', 1)
518 rest = os.path.normpath(rest) # :standardizepath
519 path = os.path.join(prefix, rest)
522 def GetInstallName(self):
523 """Return LD_DYLIB_INSTALL_NAME for this target."""
524 # Xcode sets this for shared_libraries, and for nonbundled loadable_modules.
525 if (self.spec['type'] != 'shared_library' and
526 (self.spec['type'] != 'loadable_module' or self._IsBundle())):
529 default_install_name = \
530 '$(DYLIB_INSTALL_NAME_BASE:standardizepath)/$(EXECUTABLE_PATH)'
531 install_name = self.GetPerTargetSetting(
532 'LD_DYLIB_INSTALL_NAME', default=default_install_name)
534 # Hardcode support for the variables used in chromium for now, to
535 # unblock people using the make build.
536 if '$' in install_name:
537 assert install_name in ('$(DYLIB_INSTALL_NAME_BASE:standardizepath)/'
538 '$(WRAPPER_NAME)/$(PRODUCT_NAME)', default_install_name), (
539 'Variables in LD_DYLIB_INSTALL_NAME are not generally supported '
540 'yet in target \'%s\' (got \'%s\')' %
541 (self.spec['target_name'], install_name))
543 install_name = install_name.replace(
544 '$(DYLIB_INSTALL_NAME_BASE:standardizepath)',
545 self._StandardizePath(self.GetInstallNameBase()))
547 # These are only valid for bundles, hence the |if|.
548 install_name = install_name.replace(
549 '$(WRAPPER_NAME)', self.GetWrapperName())
550 install_name = install_name.replace(
551 '$(PRODUCT_NAME)', self.GetProductName())
553 assert '$(WRAPPER_NAME)' not in install_name
554 assert '$(PRODUCT_NAME)' not in install_name
556 install_name = install_name.replace(
557 '$(EXECUTABLE_PATH)', self.GetExecutablePath())
560 def _MapLinkerFlagFilename(self, ldflag, gyp_to_build_path):
561 """Checks if ldflag contains a filename and if so remaps it from
562 gyp-directory-relative to build-directory-relative."""
563 # This list is expanded on demand.
564 # They get matched as:
565 # -exported_symbols_list file
566 # -Wl,exported_symbols_list file
567 # -Wl,exported_symbols_list,file
568 LINKER_FILE = '(\S+)'
571 ['-exported_symbols_list', LINKER_FILE], # Needed for NaCl.
572 ['-unexported_symbols_list', LINKER_FILE],
573 ['-reexported_symbols_list', LINKER_FILE],
574 ['-sectcreate', WORD, WORD, LINKER_FILE], # Needed for remoting.
576 for flag_pattern in linker_flags:
577 regex = re.compile('(?:-Wl,)?' + '[ ,]'.join(flag_pattern))
578 m = regex.match(ldflag)
580 ldflag = ldflag[:m.start(1)] + gyp_to_build_path(m.group(1)) + \
582 # Required for ffmpeg (no idea why they don't use LIBRARY_SEARCH_PATHS,
583 # TODO(thakis): Update ffmpeg.gyp):
584 if ldflag.startswith('-L'):
585 ldflag = '-L' + gyp_to_build_path(ldflag[len('-L'):])
588 def GetLdflags(self, configname, product_dir, gyp_to_build_path, arch=None):
589 """Returns flags that need to be passed to the linker.
592 configname: The name of the configuration to get ld flags for.
593 product_dir: The directory where products such static and dynamic
594 libraries are placed. This is added to the library search path.
595 gyp_to_build_path: A function that converts paths relative to the
596 current gyp file to paths relative to the build direcotry.
598 self.configname = configname
601 # The xcode build is relative to a gyp file's directory, and OTHER_LDFLAGS
602 # can contain entries that depend on this. Explicitly absolutify these.
603 for ldflag in self._Settings().get('OTHER_LDFLAGS', []):
604 ldflags.append(self._MapLinkerFlagFilename(ldflag, gyp_to_build_path))
606 if self._Test('DEAD_CODE_STRIPPING', 'YES', default='NO'):
607 ldflags.append('-Wl,-dead_strip')
609 if self._Test('PREBINDING', 'YES', default='NO'):
610 ldflags.append('-Wl,-prebind')
613 ldflags, 'DYLIB_COMPATIBILITY_VERSION', '-compatibility_version %s')
615 ldflags, 'DYLIB_CURRENT_VERSION', '-current_version %s')
617 self._AppendPlatformVersionMinFlags(ldflags)
619 if 'SDKROOT' in self._Settings():
620 ldflags.append('-isysroot ' + self._SdkPath())
622 for library_path in self._Settings().get('LIBRARY_SEARCH_PATHS', []):
623 ldflags.append('-L' + gyp_to_build_path(library_path))
625 if 'ORDER_FILE' in self._Settings():
626 ldflags.append('-Wl,-order_file ' +
627 '-Wl,' + gyp_to_build_path(
628 self._Settings()['ORDER_FILE']))
633 archs = self._Settings().get('ARCHS', ['i386'])
635 # TODO: Supporting fat binaries will be annoying.
636 self._WarnUnimplemented('ARCHS')
638 ldflags.append('-arch ' + archs[0])
640 # Xcode adds the product directory by default.
641 ldflags.append('-L' + product_dir)
643 install_name = self.GetInstallName()
644 if install_name and self.spec['type'] != 'loadable_module':
645 ldflags.append('-install_name ' + install_name.replace(' ', r'\ '))
647 for rpath in self._Settings().get('LD_RUNPATH_SEARCH_PATHS', []):
648 ldflags.append('-Wl,-rpath,' + rpath)
650 config = self.spec['configurations'][self.configname]
651 framework_dirs = config.get('mac_framework_dirs', [])
652 for directory in framework_dirs:
653 ldflags.append('-F' + directory.replace('$(SDKROOT)', self._SdkPath()))
655 self.configname = None
658 def GetLibtoolflags(self, configname):
659 """Returns flags that need to be passed to the static linker.
662 configname: The name of the configuration to get ld flags for.
664 self.configname = configname
667 for libtoolflag in self._Settings().get('OTHER_LDFLAGS', []):
668 libtoolflags.append(libtoolflag)
669 # TODO(thakis): ARCHS?
671 self.configname = None
674 def GetPerTargetSettings(self):
675 """Gets a list of all the per-target settings. This will only fetch keys
676 whose values are the same across all configurations."""
679 for configname in sorted(self.xcode_settings.keys()):
681 result = dict(self.xcode_settings[configname])
684 for key, value in self.xcode_settings[configname].iteritems():
685 if key not in result:
687 elif result[key] != value:
691 def GetPerConfigSetting(self, setting, configname, default=None):
692 if configname in self.xcode_settings:
693 return self.xcode_settings[configname].get(setting, default)
695 return self.GetPerTargetSetting(setting, default)
697 def GetPerTargetSetting(self, setting, default=None):
698 """Tries to get xcode_settings.setting from spec. Assumes that the setting
699 has the same value in all configurations and throws otherwise."""
702 for configname in sorted(self.xcode_settings.keys()):
704 result = self.xcode_settings[configname].get(setting, None)
705 is_first_pass = False
707 assert result == self.xcode_settings[configname].get(setting, None), (
708 "Expected per-target setting for '%s', got per-config setting "
709 "(target %s)" % (setting, self.spec['target_name']))
714 def _GetStripPostbuilds(self, configname, output_binary, quiet):
715 """Returns a list of shell commands that contain the shell commands
716 neccessary to strip this target's binary. These should be run as postbuilds
717 before the actual postbuilds run."""
718 self.configname = configname
721 if (self._Test('DEPLOYMENT_POSTPROCESSING', 'YES', default='NO') and
722 self._Test('STRIP_INSTALLED_PRODUCT', 'YES', default='NO')):
724 default_strip_style = 'debugging'
725 if self.spec['type'] == 'loadable_module' and self._IsBundle():
726 default_strip_style = 'non-global'
727 elif self.spec['type'] == 'executable':
728 default_strip_style = 'all'
730 strip_style = self._Settings().get('STRIP_STYLE', default_strip_style)
737 explicit_strip_flags = self._Settings().get('STRIPFLAGS', '')
738 if explicit_strip_flags:
739 strip_flags += ' ' + _NormalizeEnvVarReferences(explicit_strip_flags)
742 result.append('echo STRIP\\(%s\\)' % self.spec['target_name'])
743 result.append('strip %s %s' % (strip_flags, output_binary))
745 self.configname = None
748 def _GetDebugInfoPostbuilds(self, configname, output, output_binary, quiet):
749 """Returns a list of shell commands that contain the shell commands
750 neccessary to massage this target's debug information. These should be run
751 as postbuilds before the actual postbuilds run."""
752 self.configname = configname
754 # For static libraries, no dSYMs are created.
756 if (self._Test('GCC_GENERATE_DEBUGGING_SYMBOLS', 'YES', default='YES') and
758 'DEBUG_INFORMATION_FORMAT', 'dwarf-with-dsym', default='dwarf') and
759 self.spec['type'] != 'static_library'):
761 result.append('echo DSYMUTIL\\(%s\\)' % self.spec['target_name'])
762 result.append('dsymutil %s -o %s' % (output_binary, output + '.dSYM'))
764 self.configname = None
767 def _GetTargetPostbuilds(self, configname, output, output_binary,
769 """Returns a list of shell commands that contain the shell commands
770 to run as postbuilds for this target, before the actual postbuilds."""
771 # dSYMs need to build before stripping happens.
773 self._GetDebugInfoPostbuilds(configname, output, output_binary, quiet) +
774 self._GetStripPostbuilds(configname, output_binary, quiet))
776 def _GetIOSPostbuilds(self, configname, output_binary):
777 """Return a shell command to codesign the iOS output binary so it can
778 be deployed to a device. This should be run as the very last step of the
780 if not (self.isIOS and self.spec['type'] == "executable"):
783 identity = self.xcode_settings[configname].get('CODE_SIGN_IDENTITY', '')
786 if identity not in XcodeSettings._codesigning_key_cache:
787 proc = subprocess.Popen(['security', 'find-identity', '-p', 'codesigning',
788 '-v'], stdout=subprocess.PIPE)
789 output = proc.communicate()[0].strip()
791 for item in output.split("\n"):
793 assert key == None, (
794 "Multiple codesigning identities for identity: %s" %
796 key = item.split(' ')[1]
797 XcodeSettings._codesigning_key_cache[identity] = key
798 key = XcodeSettings._codesigning_key_cache[identity]
800 # Warn for any unimplemented signing xcode keys.
801 unimpl = ['CODE_SIGN_RESOURCE_RULES_PATH', 'OTHER_CODE_SIGN_FLAGS',
802 'CODE_SIGN_ENTITLEMENTS']
803 keys = set(self.xcode_settings[configname].keys())
804 unimpl = set(unimpl) & keys
806 print 'Warning: Some codesign keys not implemented, ignoring:', \
808 return ['codesign --force --sign %s %s' % (key, output_binary)]
811 def AddImplicitPostbuilds(self, configname, output, output_binary,
812 postbuilds=[], quiet=False):
813 """Returns a list of shell commands that should run before and after
815 assert output_binary is not None
816 pre = self._GetTargetPostbuilds(configname, output, output_binary, quiet)
817 post = self._GetIOSPostbuilds(configname, output_binary)
818 return pre + postbuilds + post
820 def _AdjustLibrary(self, library, config_name=None):
821 if library.endswith('.framework'):
822 l = '-framework ' + os.path.splitext(os.path.basename(library))[0]
824 m = self.library_re.match(library)
826 l = '-l' + m.group(1)
829 return l.replace('$(SDKROOT)', self._SdkPath(config_name))
831 def AdjustLibraries(self, libraries, config_name=None):
832 """Transforms entries like 'Cocoa.framework' in libraries into entries like
833 '-framework Cocoa', 'libcrypto.dylib' into '-lcrypto', etc.
835 libraries = [self._AdjustLibrary(library, config_name)
836 for library in libraries]
839 def _BuildMachineOSBuild(self):
840 return self._GetStdout(['sw_vers', '-buildVersion'])
842 def _XcodeVersion(self):
843 # `xcodebuild -version` output looks like
845 # Build version 4H1503
848 # Component versions: DevToolsCore-1809.0; DevToolsSupport-1806.0
849 # BuildVersion: 10M2518
850 # Convert that to '0463', '4H1503'.
851 version_list = self._GetStdout(['xcodebuild', '-version']).splitlines()
852 version = version_list[0]
853 build = version_list[-1]
854 # Be careful to convert "4.2" to "0420":
855 version = version.split()[-1].replace('.', '')
856 version = (version + '0' * (3 - len(version))).zfill(4)
857 build = build.split()[-1]
858 return version, build
860 def _XcodeIOSDeviceFamily(self, configname):
861 family = self.xcode_settings[configname].get('TARGETED_DEVICE_FAMILY', '1')
862 return [int(x) for x in family.split(',')]
864 def GetExtraPlistItems(self, configname=None):
865 """Returns a dictionary with extra items to insert into Info.plist."""
866 if configname not in XcodeSettings._plist_cache:
868 cache['BuildMachineOSBuild'] = self._BuildMachineOSBuild()
870 xcode, xcode_build = self._XcodeVersion()
871 cache['DTXcode'] = xcode
872 cache['DTXcodeBuild'] = xcode_build
874 sdk_root = self._SdkRoot(configname)
875 cache['DTSDKName'] = sdk_root
877 cache['DTSDKBuild'] = self._GetSdkVersionInfoItem(
878 sdk_root, 'ProductBuildVersion')
880 cache['DTSDKBuild'] = cache['BuildMachineOSBuild']
883 cache['DTPlatformName'] = cache['DTSDKName']
884 if configname.endswith("iphoneos"):
885 cache['DTPlatformVersion'] = self._GetSdkVersionInfoItem(
886 sdk_root, 'ProductVersion')
887 cache['CFBundleSupportedPlatforms'] = ['iPhoneOS']
889 cache['CFBundleSupportedPlatforms'] = ['iPhoneSimulator']
890 XcodeSettings._plist_cache[configname] = cache
892 # Include extra plist items that are per-target, not per global
894 items = dict(XcodeSettings._plist_cache[configname])
896 items['UIDeviceFamily'] = self._XcodeIOSDeviceFamily(configname)
900 class MacPrefixHeader(object):
901 """A class that helps with emulating Xcode's GCC_PREFIX_HEADER feature.
903 This feature consists of several pieces:
904 * If GCC_PREFIX_HEADER is present, all compilations in that project get an
905 additional |-include path_to_prefix_header| cflag.
906 * If GCC_PRECOMPILE_PREFIX_HEADER is present too, then the prefix header is
907 instead compiled, and all other compilations in the project get an
908 additional |-include path_to_compiled_header| instead.
909 + Compiled prefix headers have the extension gch. There is one gch file for
910 every language used in the project (c, cc, m, mm), since gch files for
911 different languages aren't compatible.
912 + gch files themselves are built with the target's normal cflags, but they
913 obviously don't get the |-include| flag. Instead, they need a -x flag that
914 describes their language.
915 + All o files in the target need to depend on the gch file, to make sure
916 it's built before any o file is built.
918 This class helps with some of these tasks, but it needs help from the build
919 system for writing dependencies to the gch files, for writing build commands
920 for the gch files, and for figuring out the location of the gch files.
922 def __init__(self, xcode_settings,
923 gyp_path_to_build_path, gyp_path_to_build_output):
924 """If xcode_settings is None, all methods on this class are no-ops.
927 gyp_path_to_build_path: A function that takes a gyp-relative path,
928 and returns a path relative to the build directory.
929 gyp_path_to_build_output: A function that takes a gyp-relative path and
930 a language code ('c', 'cc', 'm', or 'mm'), and that returns a path
931 to where the output of precompiling that path for that language
932 should be placed (without the trailing '.gch').
934 # This doesn't support per-configuration prefix headers. Good enough
937 self.compile_headers = False
939 self.header = xcode_settings.GetPerTargetSetting('GCC_PREFIX_HEADER')
940 self.compile_headers = xcode_settings.GetPerTargetSetting(
941 'GCC_PRECOMPILE_PREFIX_HEADER', default='NO') != 'NO'
942 self.compiled_headers = {}
944 if self.compile_headers:
945 for lang in ['c', 'cc', 'm', 'mm']:
946 self.compiled_headers[lang] = gyp_path_to_build_output(
948 self.header = gyp_path_to_build_path(self.header)
950 def _CompiledHeader(self, lang, arch):
951 assert self.compile_headers
952 h = self.compiled_headers[lang]
957 def GetInclude(self, lang, arch=None):
958 """Gets the cflags to include the prefix header for language |lang|."""
959 if self.compile_headers and lang in self.compiled_headers:
960 return '-include %s' % self._CompiledHeader(lang, arch)
962 return '-include %s' % self.header
966 def _Gch(self, lang, arch):
967 """Returns the actual file name of the prefix header for language |lang|."""
968 assert self.compile_headers
969 return self._CompiledHeader(lang, arch) + '.gch'
971 def GetObjDependencies(self, sources, objs, arch=None):
972 """Given a list of source files and the corresponding object files, returns
973 a list of (source, object, gch) tuples, where |gch| is the build-directory
974 relative path to the gch file each object file depends on. |compilable[i]|
975 has to be the source file belonging to |objs[i]|."""
976 if not self.header or not self.compile_headers:
980 for source, obj in zip(sources, objs):
981 ext = os.path.splitext(source)[1]
984 '.cpp': 'cc', '.cc': 'cc', '.cxx': 'cc',
989 result.append((source, obj, self._Gch(lang, arch)))
992 def GetPchBuildCommands(self, arch=None):
993 """Returns [(path_to_gch, language_flag, language, header)].
994 |path_to_gch| and |header| are relative to the build directory.
996 if not self.header or not self.compile_headers:
999 (self._Gch('c', arch), '-x c-header', 'c', self.header),
1000 (self._Gch('cc', arch), '-x c++-header', 'cc', self.header),
1001 (self._Gch('m', arch), '-x objective-c-header', 'm', self.header),
1002 (self._Gch('mm', arch), '-x objective-c++-header', 'mm', self.header),
1006 def MergeGlobalXcodeSettingsToSpec(global_dict, spec):
1007 """Merges the global xcode_settings dictionary into each configuration of the
1008 target represented by spec. For keys that are both in the global and the local
1009 xcode_settings dict, the local key gets precendence.
1011 # The xcode generator special-cases global xcode_settings and does something
1012 # that amounts to merging in the global xcode_settings into each local
1013 # xcode_settings dict.
1014 global_xcode_settings = global_dict.get('xcode_settings', {})
1015 for config in spec['configurations'].values():
1016 if 'xcode_settings' in config:
1017 new_settings = global_xcode_settings.copy()
1018 new_settings.update(config['xcode_settings'])
1019 config['xcode_settings'] = new_settings
1022 def IsMacBundle(flavor, spec):
1023 """Returns if |spec| should be treated as a bundle.
1025 Bundles are directories with a certain subdirectory structure, instead of
1026 just a single file. Bundle rules do not produce a binary but also package
1027 resources into that directory."""
1028 is_mac_bundle = (int(spec.get('mac_bundle', 0)) != 0 and flavor == 'mac')
1030 assert spec['type'] != 'none', (
1031 'mac_bundle targets cannot have type none (target "%s")' %
1032 spec['target_name'])
1033 return is_mac_bundle
1036 def GetMacBundleResources(product_dir, xcode_settings, resources):
1037 """Yields (output, resource) pairs for every resource in |resources|.
1038 Only call this for mac bundle targets.
1041 product_dir: Path to the directory containing the output bundle,
1042 relative to the build directory.
1043 xcode_settings: The XcodeSettings of the current target.
1044 resources: A list of bundle resources, relative to the build directory.
1046 dest = os.path.join(product_dir,
1047 xcode_settings.GetBundleResourceFolder())
1048 for res in resources:
1051 # The make generator doesn't support it, so forbid it everywhere
1052 # to keep the generators more interchangable.
1053 assert ' ' not in res, (
1054 "Spaces in resource filenames not supported (%s)" % res)
1056 # Split into (path,file).
1057 res_parts = os.path.split(res)
1059 # Now split the path into (prefix,maybe.lproj).
1060 lproj_parts = os.path.split(res_parts[0])
1061 # If the resource lives in a .lproj bundle, add that to the destination.
1062 if lproj_parts[1].endswith('.lproj'):
1063 output = os.path.join(output, lproj_parts[1])
1065 output = os.path.join(output, res_parts[1])
1066 # Compiled XIB files are referred to by .nib.
1067 if output.endswith('.xib'):
1068 output = os.path.splitext(output)[0] + '.nib'
1069 # Compiled storyboard files are referred to by .storyboardc.
1070 if output.endswith('.storyboard'):
1071 output = os.path.splitext(output)[0] + '.storyboardc'
1076 def GetMacInfoPlist(product_dir, xcode_settings, gyp_path_to_build_path):
1077 """Returns (info_plist, dest_plist, defines, extra_env), where:
1078 * |info_plist| is the source plist path, relative to the
1080 * |dest_plist| is the destination plist path, relative to the
1082 * |defines| is a list of preprocessor defines (empty if the plist
1083 shouldn't be preprocessed,
1084 * |extra_env| is a dict of env variables that should be exported when
1085 invoking |mac_tool copy-info-plist|.
1087 Only call this for mac bundle targets.
1090 product_dir: Path to the directory containing the output bundle,
1091 relative to the build directory.
1092 xcode_settings: The XcodeSettings of the current target.
1093 gyp_to_build_path: A function that converts paths relative to the
1094 current gyp file to paths relative to the build direcotry.
1096 info_plist = xcode_settings.GetPerTargetSetting('INFOPLIST_FILE')
1098 return None, None, [], {}
1100 # The make generator doesn't support it, so forbid it everywhere
1101 # to keep the generators more interchangable.
1102 assert ' ' not in info_plist, (
1103 "Spaces in Info.plist filenames not supported (%s)" % info_plist)
1105 info_plist = gyp_path_to_build_path(info_plist)
1107 # If explicitly set to preprocess the plist, invoke the C preprocessor and
1108 # specify any defines as -D flags.
1109 if xcode_settings.GetPerTargetSetting(
1110 'INFOPLIST_PREPROCESS', default='NO') == 'YES':
1111 # Create an intermediate file based on the path.
1112 defines = shlex.split(xcode_settings.GetPerTargetSetting(
1113 'INFOPLIST_PREPROCESSOR_DEFINITIONS', default=''))
1117 dest_plist = os.path.join(product_dir, xcode_settings.GetBundlePlistPath())
1118 extra_env = xcode_settings.GetPerTargetSettings()
1120 return info_plist, dest_plist, defines, extra_env
1123 def _GetXcodeEnv(xcode_settings, built_products_dir, srcroot, configuration,
1124 additional_settings=None):
1125 """Return the environment variables that Xcode would set. See
1126 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
1130 xcode_settings: An XcodeSettings object. If this is None, this function
1131 returns an empty dict.
1132 built_products_dir: Absolute path to the built products dir.
1133 srcroot: Absolute path to the source root.
1134 configuration: The build configuration name.
1135 additional_settings: An optional dict with more values to add to the
1138 if not xcode_settings: return {}
1140 # This function is considered a friend of XcodeSettings, so let it reach into
1141 # its implementation details.
1142 spec = xcode_settings.spec
1144 # These are filled in on a as-needed basis.
1146 'BUILT_PRODUCTS_DIR' : built_products_dir,
1147 'CONFIGURATION' : configuration,
1148 'PRODUCT_NAME' : xcode_settings.GetProductName(),
1149 # See /Developer/Platforms/MacOSX.platform/Developer/Library/Xcode/Specifications/MacOSX\ Product\ Types.xcspec for FULL_PRODUCT_NAME
1150 'SRCROOT' : srcroot,
1151 'SOURCE_ROOT': '${SRCROOT}',
1152 # This is not true for static libraries, but currently the env is only
1153 # written for bundles:
1154 'TARGET_BUILD_DIR' : built_products_dir,
1155 'TEMP_DIR' : '${TMPDIR}',
1157 if xcode_settings.GetPerConfigSetting('SDKROOT', configuration):
1158 env['SDKROOT'] = xcode_settings._SdkPath(configuration)
1162 if spec['type'] in (
1163 'executable', 'static_library', 'shared_library', 'loadable_module'):
1164 env['EXECUTABLE_NAME'] = xcode_settings.GetExecutableName()
1165 env['EXECUTABLE_PATH'] = xcode_settings.GetExecutablePath()
1166 env['FULL_PRODUCT_NAME'] = xcode_settings.GetFullProductName()
1167 mach_o_type = xcode_settings.GetMachOType()
1169 env['MACH_O_TYPE'] = mach_o_type
1170 env['PRODUCT_TYPE'] = xcode_settings.GetProductType()
1171 if xcode_settings._IsBundle():
1172 env['CONTENTS_FOLDER_PATH'] = \
1173 xcode_settings.GetBundleContentsFolderPath()
1174 env['UNLOCALIZED_RESOURCES_FOLDER_PATH'] = \
1175 xcode_settings.GetBundleResourceFolder()
1176 env['INFOPLIST_PATH'] = xcode_settings.GetBundlePlistPath()
1177 env['WRAPPER_NAME'] = xcode_settings.GetWrapperName()
1179 install_name = xcode_settings.GetInstallName()
1181 env['LD_DYLIB_INSTALL_NAME'] = install_name
1182 install_name_base = xcode_settings.GetInstallNameBase()
1183 if install_name_base:
1184 env['DYLIB_INSTALL_NAME_BASE'] = install_name_base
1186 if not additional_settings:
1187 additional_settings = {}
1189 # Flatten lists to strings.
1190 for k in additional_settings:
1191 if not isinstance(additional_settings[k], str):
1192 additional_settings[k] = ' '.join(additional_settings[k])
1193 additional_settings.update(env)
1195 for k in additional_settings:
1196 additional_settings[k] = _NormalizeEnvVarReferences(additional_settings[k])
1198 return additional_settings
1201 def _NormalizeEnvVarReferences(str):
1202 """Takes a string containing variable references in the form ${FOO}, $(FOO),
1203 or $FOO, and returns a string with all variable references in the form ${FOO}.
1206 str = re.sub(r'\$([a-zA-Z_][a-zA-Z0-9_]*)', r'${\1}', str)
1209 matches = re.findall(r'(\$\(([a-zA-Z0-9\-_]+)\))', str)
1210 for match in matches:
1211 to_replace, variable = match
1212 assert '$(' not in match, '$($(FOO)) variables not supported: ' + match
1213 str = str.replace(to_replace, '${' + variable + '}')
1218 def ExpandEnvVars(string, expansions):
1219 """Expands ${VARIABLES}, $(VARIABLES), and $VARIABLES in string per the
1220 expansions list. If the variable expands to something that references
1221 another variable, this variable is expanded as well if it's in env --
1222 until no variables present in env are left."""
1223 for k, v in reversed(expansions):
1224 string = string.replace('${' + k + '}', v)
1225 string = string.replace('$(' + k + ')', v)
1226 string = string.replace('$' + k, v)
1230 def _TopologicallySortedEnvVarKeys(env):
1231 """Takes a dict |env| whose values are strings that can refer to other keys,
1232 for example env['foo'] = '$(bar) and $(baz)'. Returns a list L of all keys of
1233 env such that key2 is after key1 in L if env[key2] refers to env[key1].
1235 Throws an Exception in case of dependency cycles.
1237 # Since environment variables can refer to other variables, the evaluation
1238 # order is important. Below is the logic to compute the dependency graph
1240 regex = re.compile(r'\$\{([a-zA-Z0-9\-_]+)\}')
1242 # Use a definition of edges such that user_of_variable -> used_varible.
1243 # This happens to be easier in this case, since a variable's
1244 # definition contains all variables it references in a single string.
1245 # We can then reverse the result of the topological sort at the end.
1246 # Since: reverse(topsort(DAG)) = topsort(reverse_edges(DAG))
1247 matches = set([v for v in regex.findall(env[node]) if v in env])
1248 for dependee in matches:
1249 assert '${' not in dependee, 'Nested variables not supported: ' + dependee
1253 # Topologically sort, and then reverse, because we used an edge definition
1254 # that's inverted from the expected result of this function (see comment
1256 order = gyp.common.TopologicallySorted(env.keys(), GetEdges)
1259 except gyp.common.CycleError, e:
1261 'Xcode environment variables are cyclically dependent: ' + str(e.nodes))
1264 def GetSortedXcodeEnv(xcode_settings, built_products_dir, srcroot,
1265 configuration, additional_settings=None):
1266 env = _GetXcodeEnv(xcode_settings, built_products_dir, srcroot, configuration,
1267 additional_settings)
1268 return [(key, env[key]) for key in _TopologicallySortedEnvVarKeys(env)]
1271 def GetSpecPostbuildCommands(spec, quiet=False):
1272 """Returns the list of postbuilds explicitly defined on |spec|, in a form
1273 executable by a shell."""
1275 for postbuild in spec.get('postbuilds', []):
1277 postbuilds.append('echo POSTBUILD\\(%s\\) %s' % (
1278 spec['target_name'], postbuild['postbuild_name']))
1279 postbuilds.append(gyp.common.EncodePOSIXShellList(postbuild['action']))
1283 def _HasIOSTarget(targets):
1284 """Returns true if any target contains the iOS specific key
1285 IPHONEOS_DEPLOYMENT_TARGET."""
1286 for target_dict in targets.values():
1287 for config in target_dict['configurations'].values():
1288 if config.get('xcode_settings', {}).get('IPHONEOS_DEPLOYMENT_TARGET'):
1293 def _AddIOSDeviceConfigurations(targets):
1294 """Clone all targets and append -iphoneos to the name. Configure these targets
1295 to build for iOS devices."""
1296 for target_dict in targets.values():
1297 for config_name in target_dict['configurations'].keys():
1298 config = target_dict['configurations'][config_name]
1299 new_config_name = config_name + '-iphoneos'
1300 new_config_dict = copy.deepcopy(config)
1301 if target_dict['toolset'] == 'target':
1302 new_config_dict['xcode_settings']['ARCHS'] = ['armv7']
1303 new_config_dict['xcode_settings']['SDKROOT'] = 'iphoneos'
1304 target_dict['configurations'][new_config_name] = new_config_dict
1307 def CloneConfigurationForDeviceAndEmulator(target_dicts):
1308 """If |target_dicts| contains any iOS targets, automatically create -iphoneos
1309 targets for iOS device builds."""
1310 if _HasIOSTarget(target_dicts):
1311 return _AddIOSDeviceConfigurations(target_dicts)