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 class XcodeSettings(object):
22 """A class that understands the gyp 'xcode_settings' object."""
24 # Populated lazily by _SdkPath(). Shared by all XcodeSettings, so cached
25 # at class-level for efficiency.
29 # Populated lazily by GetExtraPlistItems(). Shared by all XcodeSettings, so
30 # cached at class-level for efficiency.
33 # Populated lazily by GetIOSPostbuilds. Shared by all XcodeSettings, so
34 # cached at class-level for efficiency.
35 _codesigning_key_cache = {}
37 # Populated lazily by _XcodeVersion. Shared by all XcodeSettings, so cached
38 # at class-level for efficiency.
39 _xcode_version_cache = ()
41 def __init__(self, spec):
46 # Per-target 'xcode_settings' are pushed down into configs earlier by gyp.
47 # This means self.xcode_settings[config] always contains all settings
48 # for that config -- the per-target settings as well. Settings that are
49 # the same for all configs are implicitly per-target settings.
50 self.xcode_settings = {}
51 configs = spec['configurations']
52 for configname, config in configs.iteritems():
53 self.xcode_settings[configname] = config.get('xcode_settings', {})
54 self._ConvertConditionalKeys(configname)
55 if self.xcode_settings[configname].get('IPHONEOS_DEPLOYMENT_TARGET',
59 # This is only non-None temporarily during the execution of some methods.
60 self.configname = None
62 # Used by _AdjustLibrary to match .a and .dylib entries in libraries.
63 self.library_re = re.compile(r'^lib([^/]+)\.(a|dylib)$')
65 def _ConvertConditionalKeys(self, configname):
66 """Converts or warns on conditional keys. Xcode supports conditional keys,
67 such as CODE_SIGN_IDENTITY[sdk=iphoneos*]. This is a partial implementation
68 with some keys converted while the rest force a warning."""
69 settings = self.xcode_settings[configname]
70 conditional_keys = [key for key in settings if key.endswith(']')]
71 for key in conditional_keys:
72 # If you need more, speak up at http://crbug.com/122592
73 if key.endswith("[sdk=iphoneos*]"):
74 if configname.endswith("iphoneos"):
75 new_key = key.split("[")[0]
76 settings[new_key] = settings[key]
78 print 'Warning: Conditional keys not implemented, ignoring:', \
79 ' '.join(conditional_keys)
83 assert self.configname
84 return self.xcode_settings[self.configname]
86 def _Test(self, test_key, cond_key, default):
87 return self._Settings().get(test_key, default) == cond_key
89 def _Appendf(self, lst, test_key, format_str, default=None):
90 if test_key in self._Settings():
91 lst.append(format_str % str(self._Settings()[test_key]))
93 lst.append(format_str % str(default))
95 def _WarnUnimplemented(self, test_key):
96 if test_key in self._Settings():
97 print 'Warning: Ignoring not yet implemented key "%s".' % test_key
100 return int(self.spec.get('mac_bundle', 0)) != 0
102 def GetFrameworkVersion(self):
103 """Returns the framework version of the current target. Only valid for
105 assert self._IsBundle()
106 return self.GetPerTargetSetting('FRAMEWORK_VERSION', default='A')
108 def GetWrapperExtension(self):
109 """Returns the bundle extension (.app, .framework, .plugin, etc). Only
110 valid for bundles."""
111 assert self._IsBundle()
112 if self.spec['type'] in ('loadable_module', 'shared_library'):
113 default_wrapper_extension = {
114 'loadable_module': 'bundle',
115 'shared_library': 'framework',
117 wrapper_extension = self.GetPerTargetSetting(
118 'WRAPPER_EXTENSION', default=default_wrapper_extension)
119 return '.' + self.spec.get('product_extension', wrapper_extension)
120 elif self.spec['type'] == 'executable':
121 return '.' + self.spec.get('product_extension', 'app')
123 assert False, "Don't know extension for '%s', target '%s'" % (
124 self.spec['type'], self.spec['target_name'])
126 def GetProductName(self):
127 """Returns PRODUCT_NAME."""
128 return self.spec.get('product_name', self.spec['target_name'])
130 def GetFullProductName(self):
131 """Returns FULL_PRODUCT_NAME."""
133 return self.GetWrapperName()
135 return self._GetStandaloneBinaryPath()
137 def GetWrapperName(self):
138 """Returns the directory name of the bundle represented by this target.
139 Only valid for bundles."""
140 assert self._IsBundle()
141 return self.GetProductName() + self.GetWrapperExtension()
143 def GetBundleContentsFolderPath(self):
144 """Returns the qualified path to the bundle's contents folder. E.g.
145 Chromium.app/Contents or Foo.bundle/Versions/A. Only valid for bundles."""
147 return self.GetWrapperName()
148 assert self._IsBundle()
149 if self.spec['type'] == 'shared_library':
151 self.GetWrapperName(), 'Versions', self.GetFrameworkVersion())
153 # loadable_modules have a 'Contents' folder like executables.
154 return os.path.join(self.GetWrapperName(), 'Contents')
156 def GetBundleResourceFolder(self):
157 """Returns the qualified path to the bundle's resource folder. E.g.
158 Chromium.app/Contents/Resources. Only valid for bundles."""
159 assert self._IsBundle()
161 return self.GetBundleContentsFolderPath()
162 return os.path.join(self.GetBundleContentsFolderPath(), 'Resources')
164 def GetBundlePlistPath(self):
165 """Returns the qualified path to the bundle's plist file. E.g.
166 Chromium.app/Contents/Info.plist. Only valid for bundles."""
167 assert self._IsBundle()
168 if self.spec['type'] in ('executable', 'loadable_module'):
169 return os.path.join(self.GetBundleContentsFolderPath(), 'Info.plist')
171 return os.path.join(self.GetBundleContentsFolderPath(),
172 'Resources', 'Info.plist')
174 def GetProductType(self):
175 """Returns the PRODUCT_TYPE of this target."""
178 'executable': 'com.apple.product-type.application',
179 'loadable_module': 'com.apple.product-type.bundle',
180 'shared_library': 'com.apple.product-type.framework',
184 'executable': 'com.apple.product-type.tool',
185 'loadable_module': 'com.apple.product-type.library.dynamic',
186 'shared_library': 'com.apple.product-type.library.dynamic',
187 'static_library': 'com.apple.product-type.library.static',
190 def GetMachOType(self):
191 """Returns the MACH_O_TYPE of this target."""
192 # Weird, but matches Xcode.
193 if not self._IsBundle() and self.spec['type'] == 'executable':
196 'executable': 'mh_execute',
197 'static_library': 'staticlib',
198 'shared_library': 'mh_dylib',
199 'loadable_module': 'mh_bundle',
202 def _GetBundleBinaryPath(self):
203 """Returns the name of the bundle binary of by this target.
204 E.g. Chromium.app/Contents/MacOS/Chromium. Only valid for bundles."""
205 assert self._IsBundle()
206 if self.spec['type'] in ('shared_library') or self.isIOS:
207 path = self.GetBundleContentsFolderPath()
208 elif self.spec['type'] in ('executable', 'loadable_module'):
209 path = os.path.join(self.GetBundleContentsFolderPath(), 'MacOS')
210 return os.path.join(path, self.GetExecutableName())
212 def _GetStandaloneExecutableSuffix(self):
213 if 'product_extension' in self.spec:
214 return '.' + self.spec['product_extension']
217 'static_library': '.a',
218 'shared_library': '.dylib',
219 'loadable_module': '.so',
222 def _GetStandaloneExecutablePrefix(self):
223 return self.spec.get('product_prefix', {
225 'static_library': 'lib',
226 'shared_library': 'lib',
227 # Non-bundled loadable_modules are called foo.so for some reason
228 # (that is, .so and no prefix) with the xcode build -- match that.
229 'loadable_module': '',
230 }[self.spec['type']])
232 def _GetStandaloneBinaryPath(self):
233 """Returns the name of the non-bundle binary represented by this target.
234 E.g. hello_world. Only valid for non-bundles."""
235 assert not self._IsBundle()
236 assert self.spec['type'] in (
237 'executable', 'shared_library', 'static_library', 'loadable_module'), (
238 'Unexpected type %s' % self.spec['type'])
239 target = self.spec['target_name']
240 if self.spec['type'] == 'static_library':
241 if target[:3] == 'lib':
243 elif self.spec['type'] in ('loadable_module', 'shared_library'):
244 if target[:3] == 'lib':
247 target_prefix = self._GetStandaloneExecutablePrefix()
248 target = self.spec.get('product_name', target)
249 target_ext = self._GetStandaloneExecutableSuffix()
250 return target_prefix + target + target_ext
252 def GetExecutableName(self):
253 """Returns the executable name of the bundle represented by this target.
256 return self.spec.get('product_name', self.spec['target_name'])
258 return self._GetStandaloneBinaryPath()
260 def GetExecutablePath(self):
261 """Returns the directory name of the bundle represented by this target. E.g.
262 Chromium.app/Contents/MacOS/Chromium."""
264 return self._GetBundleBinaryPath()
266 return self._GetStandaloneBinaryPath()
268 def GetActiveArchs(self, configname):
269 """Returns the architectures this target should be built for."""
270 # TODO: Look at VALID_ARCHS, ONLY_ACTIVE_ARCH; possibly set
271 # CURRENT_ARCH / NATIVE_ARCH env vars?
272 return self.xcode_settings[configname].get('ARCHS', [self._DefaultArch()])
274 def _GetStdout(self, cmdlist):
275 job = subprocess.Popen(cmdlist, stdout=subprocess.PIPE)
276 out = job.communicate()[0]
277 if job.returncode != 0:
278 sys.stderr.write(out + '\n')
279 raise GypError('Error %d running %s' % (job.returncode, cmdlist[0]))
280 return out.rstrip('\n')
282 def _GetSdkVersionInfoItem(self, sdk, infoitem):
283 return self._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():
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', [])
412 config = self.spec['configurations'][self.configname]
413 framework_dirs = config.get('mac_framework_dirs', [])
414 for directory in framework_dirs:
415 cflags.append('-F' + directory.replace('$(SDKROOT)', sdk_root))
417 self.configname = None
420 def GetCflagsC(self, configname):
421 """Returns flags that need to be added to .c, and .m compilations."""
422 self.configname = configname
424 if self._Settings().get('GCC_C_LANGUAGE_STANDARD', '') == 'ansi':
425 cflags_c.append('-ansi')
427 self._Appendf(cflags_c, 'GCC_C_LANGUAGE_STANDARD', '-std=%s')
428 cflags_c += self._Settings().get('OTHER_CFLAGS', [])
429 self.configname = None
432 def GetCflagsCC(self, configname):
433 """Returns flags that need to be added to .cc, and .mm compilations."""
434 self.configname = configname
437 clang_cxx_language_standard = self._Settings().get(
438 'CLANG_CXX_LANGUAGE_STANDARD')
439 # Note: Don't make c++0x to c++11 so that c++0x can be used with older
440 # clangs that don't understand c++11 yet (like Xcode 4.2's).
441 if clang_cxx_language_standard:
442 cflags_cc.append('-std=%s' % clang_cxx_language_standard)
444 self._Appendf(cflags_cc, 'CLANG_CXX_LIBRARY', '-stdlib=%s')
446 if self._Test('GCC_ENABLE_CPP_RTTI', 'NO', default='YES'):
447 cflags_cc.append('-fno-rtti')
448 if self._Test('GCC_ENABLE_CPP_EXCEPTIONS', 'NO', default='YES'):
449 cflags_cc.append('-fno-exceptions')
450 if self._Test('GCC_INLINES_ARE_PRIVATE_EXTERN', 'YES', default='NO'):
451 cflags_cc.append('-fvisibility-inlines-hidden')
452 if self._Test('GCC_THREADSAFE_STATICS', 'NO', default='YES'):
453 cflags_cc.append('-fno-threadsafe-statics')
454 # Note: This flag is a no-op for clang, it only has an effect for gcc.
455 if self._Test('GCC_WARN_ABOUT_INVALID_OFFSETOF_MACRO', 'NO', default='YES'):
456 cflags_cc.append('-Wno-invalid-offsetof')
460 for flag in self._Settings().get('OTHER_CPLUSPLUSFLAGS', ['$(inherited)']):
461 # TODO: More general variable expansion. Missing in many other places too.
462 if flag in ('$inherited', '$(inherited)', '${inherited}'):
463 flag = '$OTHER_CFLAGS'
464 if flag in ('$OTHER_CFLAGS', '$(OTHER_CFLAGS)', '${OTHER_CFLAGS}'):
465 other_ccflags += self._Settings().get('OTHER_CFLAGS', [])
467 other_ccflags.append(flag)
468 cflags_cc += other_ccflags
470 self.configname = None
473 def _AddObjectiveCGarbageCollectionFlags(self, flags):
474 gc_policy = self._Settings().get('GCC_ENABLE_OBJC_GC', 'unsupported')
475 if gc_policy == 'supported':
476 flags.append('-fobjc-gc')
477 elif gc_policy == 'required':
478 flags.append('-fobjc-gc-only')
480 def _AddObjectiveCARCFlags(self, flags):
481 if self._Test('CLANG_ENABLE_OBJC_ARC', 'YES', default='NO'):
482 flags.append('-fobjc-arc')
484 def _AddObjectiveCMissingPropertySynthesisFlags(self, flags):
485 if self._Test('CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS',
486 'YES', default='NO'):
487 flags.append('-Wobjc-missing-property-synthesis')
489 def GetCflagsObjC(self, configname):
490 """Returns flags that need to be added to .m compilations."""
491 self.configname = configname
493 self._AddObjectiveCGarbageCollectionFlags(cflags_objc)
494 self._AddObjectiveCARCFlags(cflags_objc)
495 self._AddObjectiveCMissingPropertySynthesisFlags(cflags_objc)
496 self.configname = None
499 def GetCflagsObjCC(self, configname):
500 """Returns flags that need to be added to .mm compilations."""
501 self.configname = configname
503 self._AddObjectiveCGarbageCollectionFlags(cflags_objcc)
504 self._AddObjectiveCARCFlags(cflags_objcc)
505 self._AddObjectiveCMissingPropertySynthesisFlags(cflags_objcc)
506 if self._Test('GCC_OBJC_CALL_CXX_CDTORS', 'YES', default='NO'):
507 cflags_objcc.append('-fobjc-call-cxx-cdtors')
508 self.configname = None
511 def GetInstallNameBase(self):
512 """Return DYLIB_INSTALL_NAME_BASE for this target."""
513 # Xcode sets this for shared_libraries, and for nonbundled loadable_modules.
514 if (self.spec['type'] != 'shared_library' and
515 (self.spec['type'] != 'loadable_module' or self._IsBundle())):
517 install_base = self.GetPerTargetSetting(
518 'DYLIB_INSTALL_NAME_BASE',
519 default='/Library/Frameworks' if self._IsBundle() else '/usr/local/lib')
522 def _StandardizePath(self, path):
523 """Do :standardizepath processing for path."""
524 # I'm not quite sure what :standardizepath does. Just call normpath(),
525 # but don't let @executable_path/../foo collapse to foo.
527 prefix, rest = '', path
528 if path.startswith('@'):
529 prefix, rest = path.split('/', 1)
530 rest = os.path.normpath(rest) # :standardizepath
531 path = os.path.join(prefix, rest)
534 def GetInstallName(self):
535 """Return LD_DYLIB_INSTALL_NAME for this target."""
536 # Xcode sets this for shared_libraries, and for nonbundled loadable_modules.
537 if (self.spec['type'] != 'shared_library' and
538 (self.spec['type'] != 'loadable_module' or self._IsBundle())):
541 default_install_name = \
542 '$(DYLIB_INSTALL_NAME_BASE:standardizepath)/$(EXECUTABLE_PATH)'
543 install_name = self.GetPerTargetSetting(
544 'LD_DYLIB_INSTALL_NAME', default=default_install_name)
546 # Hardcode support for the variables used in chromium for now, to
547 # unblock people using the make build.
548 if '$' in install_name:
549 assert install_name in ('$(DYLIB_INSTALL_NAME_BASE:standardizepath)/'
550 '$(WRAPPER_NAME)/$(PRODUCT_NAME)', default_install_name), (
551 'Variables in LD_DYLIB_INSTALL_NAME are not generally supported '
552 'yet in target \'%s\' (got \'%s\')' %
553 (self.spec['target_name'], install_name))
555 install_name = install_name.replace(
556 '$(DYLIB_INSTALL_NAME_BASE:standardizepath)',
557 self._StandardizePath(self.GetInstallNameBase()))
559 # These are only valid for bundles, hence the |if|.
560 install_name = install_name.replace(
561 '$(WRAPPER_NAME)', self.GetWrapperName())
562 install_name = install_name.replace(
563 '$(PRODUCT_NAME)', self.GetProductName())
565 assert '$(WRAPPER_NAME)' not in install_name
566 assert '$(PRODUCT_NAME)' not in install_name
568 install_name = install_name.replace(
569 '$(EXECUTABLE_PATH)', self.GetExecutablePath())
572 def _MapLinkerFlagFilename(self, ldflag, gyp_to_build_path):
573 """Checks if ldflag contains a filename and if so remaps it from
574 gyp-directory-relative to build-directory-relative."""
575 # This list is expanded on demand.
576 # They get matched as:
577 # -exported_symbols_list file
578 # -Wl,exported_symbols_list file
579 # -Wl,exported_symbols_list,file
580 LINKER_FILE = '(\S+)'
583 ['-exported_symbols_list', LINKER_FILE], # Needed for NaCl.
584 ['-unexported_symbols_list', LINKER_FILE],
585 ['-reexported_symbols_list', LINKER_FILE],
586 ['-sectcreate', WORD, WORD, LINKER_FILE], # Needed for remoting.
588 for flag_pattern in linker_flags:
589 regex = re.compile('(?:-Wl,)?' + '[ ,]'.join(flag_pattern))
590 m = regex.match(ldflag)
592 ldflag = ldflag[:m.start(1)] + gyp_to_build_path(m.group(1)) + \
594 # Required for ffmpeg (no idea why they don't use LIBRARY_SEARCH_PATHS,
595 # TODO(thakis): Update ffmpeg.gyp):
596 if ldflag.startswith('-L'):
597 ldflag = '-L' + gyp_to_build_path(ldflag[len('-L'):])
600 def GetLdflags(self, configname, product_dir, gyp_to_build_path, arch=None):
601 """Returns flags that need to be passed to the linker.
604 configname: The name of the configuration to get ld flags for.
605 product_dir: The directory where products such static and dynamic
606 libraries are placed. This is added to the library search path.
607 gyp_to_build_path: A function that converts paths relative to the
608 current gyp file to paths relative to the build direcotry.
610 self.configname = configname
613 # The xcode build is relative to a gyp file's directory, and OTHER_LDFLAGS
614 # can contain entries that depend on this. Explicitly absolutify these.
615 for ldflag in self._Settings().get('OTHER_LDFLAGS', []):
616 ldflags.append(self._MapLinkerFlagFilename(ldflag, gyp_to_build_path))
618 if self._Test('DEAD_CODE_STRIPPING', 'YES', default='NO'):
619 ldflags.append('-Wl,-dead_strip')
621 if self._Test('PREBINDING', 'YES', default='NO'):
622 ldflags.append('-Wl,-prebind')
625 ldflags, 'DYLIB_COMPATIBILITY_VERSION', '-compatibility_version %s')
627 ldflags, 'DYLIB_CURRENT_VERSION', '-current_version %s')
629 self._AppendPlatformVersionMinFlags(ldflags)
631 if 'SDKROOT' in self._Settings():
632 ldflags.append('-isysroot ' + self._SdkPath())
634 for library_path in self._Settings().get('LIBRARY_SEARCH_PATHS', []):
635 ldflags.append('-L' + gyp_to_build_path(library_path))
637 if 'ORDER_FILE' in self._Settings():
638 ldflags.append('-Wl,-order_file ' +
639 '-Wl,' + gyp_to_build_path(
640 self._Settings()['ORDER_FILE']))
645 archs = self._Settings().get('ARCHS', [self._DefaultArch()])
647 # TODO: Supporting fat binaries will be annoying.
648 self._WarnUnimplemented('ARCHS')
650 ldflags.append('-arch ' + archs[0])
652 # Xcode adds the product directory by default.
653 ldflags.append('-L' + product_dir)
655 install_name = self.GetInstallName()
656 if install_name and self.spec['type'] != 'loadable_module':
657 ldflags.append('-install_name ' + install_name.replace(' ', r'\ '))
659 for rpath in self._Settings().get('LD_RUNPATH_SEARCH_PATHS', []):
660 ldflags.append('-Wl,-rpath,' + rpath)
662 config = self.spec['configurations'][self.configname]
663 framework_dirs = config.get('mac_framework_dirs', [])
664 for directory in framework_dirs:
665 ldflags.append('-F' + directory.replace('$(SDKROOT)', self._SdkPath()))
667 self.configname = None
670 def GetLibtoolflags(self, configname):
671 """Returns flags that need to be passed to the static linker.
674 configname: The name of the configuration to get ld flags for.
676 self.configname = configname
679 for libtoolflag in self._Settings().get('OTHER_LDFLAGS', []):
680 libtoolflags.append(libtoolflag)
681 # TODO(thakis): ARCHS?
683 self.configname = None
686 def GetPerTargetSettings(self):
687 """Gets a list of all the per-target settings. This will only fetch keys
688 whose values are the same across all configurations."""
691 for configname in sorted(self.xcode_settings.keys()):
693 result = dict(self.xcode_settings[configname])
696 for key, value in self.xcode_settings[configname].iteritems():
697 if key not in result:
699 elif result[key] != value:
703 def GetPerConfigSetting(self, setting, configname, default=None):
704 if configname in self.xcode_settings:
705 return self.xcode_settings[configname].get(setting, default)
707 return self.GetPerTargetSetting(setting, default)
709 def GetPerTargetSetting(self, setting, default=None):
710 """Tries to get xcode_settings.setting from spec. Assumes that the setting
711 has the same value in all configurations and throws otherwise."""
714 for configname in sorted(self.xcode_settings.keys()):
716 result = self.xcode_settings[configname].get(setting, None)
717 is_first_pass = False
719 assert result == self.xcode_settings[configname].get(setting, None), (
720 "Expected per-target setting for '%s', got per-config setting "
721 "(target %s)" % (setting, self.spec['target_name']))
726 def _GetStripPostbuilds(self, configname, output_binary, quiet):
727 """Returns a list of shell commands that contain the shell commands
728 neccessary to strip this target's binary. These should be run as postbuilds
729 before the actual postbuilds run."""
730 self.configname = configname
733 if (self._Test('DEPLOYMENT_POSTPROCESSING', 'YES', default='NO') and
734 self._Test('STRIP_INSTALLED_PRODUCT', 'YES', default='NO')):
736 default_strip_style = 'debugging'
737 if self.spec['type'] == 'loadable_module' and self._IsBundle():
738 default_strip_style = 'non-global'
739 elif self.spec['type'] == 'executable':
740 default_strip_style = 'all'
742 strip_style = self._Settings().get('STRIP_STYLE', default_strip_style)
749 explicit_strip_flags = self._Settings().get('STRIPFLAGS', '')
750 if explicit_strip_flags:
751 strip_flags += ' ' + _NormalizeEnvVarReferences(explicit_strip_flags)
754 result.append('echo STRIP\\(%s\\)' % self.spec['target_name'])
755 result.append('strip %s %s' % (strip_flags, output_binary))
757 self.configname = None
760 def _GetDebugInfoPostbuilds(self, configname, output, output_binary, quiet):
761 """Returns a list of shell commands that contain the shell commands
762 neccessary to massage this target's debug information. These should be run
763 as postbuilds before the actual postbuilds run."""
764 self.configname = configname
766 # For static libraries, no dSYMs are created.
768 if (self._Test('GCC_GENERATE_DEBUGGING_SYMBOLS', 'YES', default='YES') and
770 'DEBUG_INFORMATION_FORMAT', 'dwarf-with-dsym', default='dwarf') and
771 self.spec['type'] != 'static_library'):
773 result.append('echo DSYMUTIL\\(%s\\)' % self.spec['target_name'])
774 result.append('dsymutil %s -o %s' % (output_binary, output + '.dSYM'))
776 self.configname = None
779 def _GetTargetPostbuilds(self, configname, output, output_binary,
781 """Returns a list of shell commands that contain the shell commands
782 to run as postbuilds for this target, before the actual postbuilds."""
783 # dSYMs need to build before stripping happens.
785 self._GetDebugInfoPostbuilds(configname, output, output_binary, quiet) +
786 self._GetStripPostbuilds(configname, output_binary, quiet))
788 def _GetIOSPostbuilds(self, configname, output_binary):
789 """Return a shell command to codesign the iOS output binary so it can
790 be deployed to a device. This should be run as the very last step of the
792 if not (self.isIOS and self.spec['type'] == "executable"):
795 settings = self.xcode_settings[configname]
796 key = self._GetIOSCodeSignIdentityKey(settings)
800 # Warn for any unimplemented signing xcode keys.
801 unimpl = ['OTHER_CODE_SIGN_FLAGS']
802 unimpl = set(unimpl) & set(self.xcode_settings[configname].keys())
804 print 'Warning: Some codesign keys not implemented, ignoring: %s' % (
805 ', '.join(sorted(unimpl)))
807 return ['%s code-sign-bundle "%s" "%s" "%s" "%s"' % (
808 os.path.join('${TARGET_BUILD_DIR}', 'gyp-mac-tool'), key,
809 settings.get('CODE_SIGN_RESOURCE_RULES_PATH', ''),
810 settings.get('CODE_SIGN_ENTITLEMENTS', ''),
811 settings.get('PROVISIONING_PROFILE', ''))
814 def _GetIOSCodeSignIdentityKey(self, settings):
815 identity = settings.get('CODE_SIGN_IDENTITY')
818 if identity not in XcodeSettings._codesigning_key_cache:
819 output = subprocess.check_output(
820 ['security', 'find-identity', '-p', 'codesigning', '-v'])
821 for line in output.splitlines():
823 assert identity not in XcodeSettings._codesigning_key_cache, (
824 "Multiple codesigning identities for identity: %s" % identity)
825 XcodeSettings._codesigning_key_cache[identity] = line.split()[1]
826 return XcodeSettings._codesigning_key_cache.get(identity, '')
828 def AddImplicitPostbuilds(self, configname, output, output_binary,
829 postbuilds=[], quiet=False):
830 """Returns a list of shell commands that should run before and after
832 assert output_binary is not None
833 pre = self._GetTargetPostbuilds(configname, output, output_binary, quiet)
834 post = self._GetIOSPostbuilds(configname, output_binary)
835 return pre + postbuilds + post
837 def _AdjustLibrary(self, library, config_name=None):
838 if library.endswith('.framework'):
839 l = '-framework ' + os.path.splitext(os.path.basename(library))[0]
841 m = self.library_re.match(library)
843 l = '-l' + m.group(1)
846 return l.replace('$(SDKROOT)', self._SdkPath(config_name))
848 def AdjustLibraries(self, libraries, config_name=None):
849 """Transforms entries like 'Cocoa.framework' in libraries into entries like
850 '-framework Cocoa', 'libcrypto.dylib' into '-lcrypto', etc.
852 libraries = [self._AdjustLibrary(library, config_name)
853 for library in libraries]
856 def _BuildMachineOSBuild(self):
857 return self._GetStdout(['sw_vers', '-buildVersion'])
859 def _XcodeVersion(self):
860 # `xcodebuild -version` output looks like
862 # Build version 4H1503
865 # Component versions: DevToolsCore-1809.0; DevToolsSupport-1806.0
866 # BuildVersion: 10M2518
867 # Convert that to '0463', '4H1503'.
868 if len(XcodeSettings._xcode_version_cache) == 0:
869 version_list = self._GetStdout(['xcodebuild', '-version']).splitlines()
870 version = version_list[0]
871 build = version_list[-1]
872 # Be careful to convert "4.2" to "0420":
873 version = version.split()[-1].replace('.', '')
874 version = (version + '0' * (3 - len(version))).zfill(4)
875 build = build.split()[-1]
876 XcodeSettings._xcode_version_cache = (version, build)
877 return XcodeSettings._xcode_version_cache
879 def _XcodeIOSDeviceFamily(self, configname):
880 family = self.xcode_settings[configname].get('TARGETED_DEVICE_FAMILY', '1')
881 return [int(x) for x in family.split(',')]
883 def GetExtraPlistItems(self, configname=None):
884 """Returns a dictionary with extra items to insert into Info.plist."""
885 if configname not in XcodeSettings._plist_cache:
887 cache['BuildMachineOSBuild'] = self._BuildMachineOSBuild()
889 xcode, xcode_build = self._XcodeVersion()
890 cache['DTXcode'] = xcode
891 cache['DTXcodeBuild'] = xcode_build
893 sdk_root = self._SdkRoot(configname)
895 sdk_root = self._DefaultSdkRoot()
896 cache['DTSDKName'] = sdk_root
898 cache['DTSDKBuild'] = self._GetSdkVersionInfoItem(
899 sdk_root, 'ProductBuildVersion')
901 cache['DTSDKBuild'] = cache['BuildMachineOSBuild']
904 cache['DTPlatformName'] = cache['DTSDKName']
905 if configname.endswith("iphoneos"):
906 cache['DTPlatformVersion'] = self._GetSdkVersionInfoItem(
907 sdk_root, 'ProductVersion')
908 cache['CFBundleSupportedPlatforms'] = ['iPhoneOS']
910 cache['CFBundleSupportedPlatforms'] = ['iPhoneSimulator']
911 XcodeSettings._plist_cache[configname] = cache
913 # Include extra plist items that are per-target, not per global
915 items = dict(XcodeSettings._plist_cache[configname])
917 items['UIDeviceFamily'] = self._XcodeIOSDeviceFamily(configname)
920 def _DefaultSdkRoot(self):
921 """Returns the default SDKROOT to use.
923 Prior to version 5.0.0, if SDKROOT was not explicitly set in the Xcode
924 project, then the environment variable was empty. Starting with this
925 version, Xcode uses the name of the newest SDK installed.
927 if self._XcodeVersion() < '0500':
929 default_sdk_path = self._XcodeSdkPath('')
930 default_sdk_root = XcodeSettings._sdk_root_cache.get(default_sdk_path)
932 return default_sdk_root
933 all_sdks = self._GetStdout(['xcodebuild', '-showsdks'])
934 for line in all_sdks.splitlines():
936 if len(items) >= 3 and items[-2] == '-sdk':
938 sdk_path = self._XcodeSdkPath(sdk_root)
939 if sdk_path == default_sdk_path:
943 def _DefaultArch(self):
944 # For Mac projects, Xcode changed the default value used when ARCHS is not
945 # set from "i386" to "x86_64".
947 # For iOS projects, if ARCHS is unset, it defaults to "armv7 armv7s" when
948 # building for a device, and the simulator binaries are always build for
951 # For new projects, ARCHS is set to $(ARCHS_STANDARD_INCLUDING_64_BIT),
952 # which correspond to "armv7 armv7s arm64", and when building the simulator
953 # the architecture is either "i386" or "x86_64" depending on the simulated
954 # device (respectively 32-bit or 64-bit device).
956 # Since the value returned by this function is only used when ARCHS is not
957 # set, then on iOS we return "i386", as the default xcode project generator
958 # does not set ARCHS if it is not set in the .gyp file.
961 version, build = self._XcodeVersion()
962 if version >= '0500':
966 class MacPrefixHeader(object):
967 """A class that helps with emulating Xcode's GCC_PREFIX_HEADER feature.
969 This feature consists of several pieces:
970 * If GCC_PREFIX_HEADER is present, all compilations in that project get an
971 additional |-include path_to_prefix_header| cflag.
972 * If GCC_PRECOMPILE_PREFIX_HEADER is present too, then the prefix header is
973 instead compiled, and all other compilations in the project get an
974 additional |-include path_to_compiled_header| instead.
975 + Compiled prefix headers have the extension gch. There is one gch file for
976 every language used in the project (c, cc, m, mm), since gch files for
977 different languages aren't compatible.
978 + gch files themselves are built with the target's normal cflags, but they
979 obviously don't get the |-include| flag. Instead, they need a -x flag that
980 describes their language.
981 + All o files in the target need to depend on the gch file, to make sure
982 it's built before any o file is built.
984 This class helps with some of these tasks, but it needs help from the build
985 system for writing dependencies to the gch files, for writing build commands
986 for the gch files, and for figuring out the location of the gch files.
988 def __init__(self, xcode_settings,
989 gyp_path_to_build_path, gyp_path_to_build_output):
990 """If xcode_settings is None, all methods on this class are no-ops.
993 gyp_path_to_build_path: A function that takes a gyp-relative path,
994 and returns a path relative to the build directory.
995 gyp_path_to_build_output: A function that takes a gyp-relative path and
996 a language code ('c', 'cc', 'm', or 'mm'), and that returns a path
997 to where the output of precompiling that path for that language
998 should be placed (without the trailing '.gch').
1000 # This doesn't support per-configuration prefix headers. Good enough
1003 self.compile_headers = False
1005 self.header = xcode_settings.GetPerTargetSetting('GCC_PREFIX_HEADER')
1006 self.compile_headers = xcode_settings.GetPerTargetSetting(
1007 'GCC_PRECOMPILE_PREFIX_HEADER', default='NO') != 'NO'
1008 self.compiled_headers = {}
1010 if self.compile_headers:
1011 for lang in ['c', 'cc', 'm', 'mm']:
1012 self.compiled_headers[lang] = gyp_path_to_build_output(
1014 self.header = gyp_path_to_build_path(self.header)
1016 def _CompiledHeader(self, lang, arch):
1017 assert self.compile_headers
1018 h = self.compiled_headers[lang]
1023 def GetInclude(self, lang, arch=None):
1024 """Gets the cflags to include the prefix header for language |lang|."""
1025 if self.compile_headers and lang in self.compiled_headers:
1026 return '-include %s' % self._CompiledHeader(lang, arch)
1028 return '-include %s' % self.header
1032 def _Gch(self, lang, arch):
1033 """Returns the actual file name of the prefix header for language |lang|."""
1034 assert self.compile_headers
1035 return self._CompiledHeader(lang, arch) + '.gch'
1037 def GetObjDependencies(self, sources, objs, arch=None):
1038 """Given a list of source files and the corresponding object files, returns
1039 a list of (source, object, gch) tuples, where |gch| is the build-directory
1040 relative path to the gch file each object file depends on. |compilable[i]|
1041 has to be the source file belonging to |objs[i]|."""
1042 if not self.header or not self.compile_headers:
1046 for source, obj in zip(sources, objs):
1047 ext = os.path.splitext(source)[1]
1050 '.cpp': 'cc', '.cc': 'cc', '.cxx': 'cc',
1055 result.append((source, obj, self._Gch(lang, arch)))
1058 def GetPchBuildCommands(self, arch=None):
1059 """Returns [(path_to_gch, language_flag, language, header)].
1060 |path_to_gch| and |header| are relative to the build directory.
1062 if not self.header or not self.compile_headers:
1065 (self._Gch('c', arch), '-x c-header', 'c', self.header),
1066 (self._Gch('cc', arch), '-x c++-header', 'cc', self.header),
1067 (self._Gch('m', arch), '-x objective-c-header', 'm', self.header),
1068 (self._Gch('mm', arch), '-x objective-c++-header', 'mm', self.header),
1072 def MergeGlobalXcodeSettingsToSpec(global_dict, spec):
1073 """Merges the global xcode_settings dictionary into each configuration of the
1074 target represented by spec. For keys that are both in the global and the local
1075 xcode_settings dict, the local key gets precendence.
1077 # The xcode generator special-cases global xcode_settings and does something
1078 # that amounts to merging in the global xcode_settings into each local
1079 # xcode_settings dict.
1080 global_xcode_settings = global_dict.get('xcode_settings', {})
1081 for config in spec['configurations'].values():
1082 if 'xcode_settings' in config:
1083 new_settings = global_xcode_settings.copy()
1084 new_settings.update(config['xcode_settings'])
1085 config['xcode_settings'] = new_settings
1088 def IsMacBundle(flavor, spec):
1089 """Returns if |spec| should be treated as a bundle.
1091 Bundles are directories with a certain subdirectory structure, instead of
1092 just a single file. Bundle rules do not produce a binary but also package
1093 resources into that directory."""
1094 is_mac_bundle = (int(spec.get('mac_bundle', 0)) != 0 and flavor == 'mac')
1096 assert spec['type'] != 'none', (
1097 'mac_bundle targets cannot have type none (target "%s")' %
1098 spec['target_name'])
1099 return is_mac_bundle
1102 def GetMacBundleResources(product_dir, xcode_settings, resources):
1103 """Yields (output, resource) pairs for every resource in |resources|.
1104 Only call this for mac bundle targets.
1107 product_dir: Path to the directory containing the output bundle,
1108 relative to the build directory.
1109 xcode_settings: The XcodeSettings of the current target.
1110 resources: A list of bundle resources, relative to the build directory.
1112 dest = os.path.join(product_dir,
1113 xcode_settings.GetBundleResourceFolder())
1114 for res in resources:
1117 # The make generator doesn't support it, so forbid it everywhere
1118 # to keep the generators more interchangable.
1119 assert ' ' not in res, (
1120 "Spaces in resource filenames not supported (%s)" % res)
1122 # Split into (path,file).
1123 res_parts = os.path.split(res)
1125 # Now split the path into (prefix,maybe.lproj).
1126 lproj_parts = os.path.split(res_parts[0])
1127 # If the resource lives in a .lproj bundle, add that to the destination.
1128 if lproj_parts[1].endswith('.lproj'):
1129 output = os.path.join(output, lproj_parts[1])
1131 output = os.path.join(output, res_parts[1])
1132 # Compiled XIB files are referred to by .nib.
1133 if output.endswith('.xib'):
1134 output = os.path.splitext(output)[0] + '.nib'
1135 # Compiled storyboard files are referred to by .storyboardc.
1136 if output.endswith('.storyboard'):
1137 output = os.path.splitext(output)[0] + '.storyboardc'
1142 def GetMacInfoPlist(product_dir, xcode_settings, gyp_path_to_build_path):
1143 """Returns (info_plist, dest_plist, defines, extra_env), where:
1144 * |info_plist| is the source plist path, relative to the
1146 * |dest_plist| is the destination plist path, relative to the
1148 * |defines| is a list of preprocessor defines (empty if the plist
1149 shouldn't be preprocessed,
1150 * |extra_env| is a dict of env variables that should be exported when
1151 invoking |mac_tool copy-info-plist|.
1153 Only call this for mac bundle targets.
1156 product_dir: Path to the directory containing the output bundle,
1157 relative to the build directory.
1158 xcode_settings: The XcodeSettings of the current target.
1159 gyp_to_build_path: A function that converts paths relative to the
1160 current gyp file to paths relative to the build direcotry.
1162 info_plist = xcode_settings.GetPerTargetSetting('INFOPLIST_FILE')
1164 return None, None, [], {}
1166 # The make generator doesn't support it, so forbid it everywhere
1167 # to keep the generators more interchangable.
1168 assert ' ' not in info_plist, (
1169 "Spaces in Info.plist filenames not supported (%s)" % info_plist)
1171 info_plist = gyp_path_to_build_path(info_plist)
1173 # If explicitly set to preprocess the plist, invoke the C preprocessor and
1174 # specify any defines as -D flags.
1175 if xcode_settings.GetPerTargetSetting(
1176 'INFOPLIST_PREPROCESS', default='NO') == 'YES':
1177 # Create an intermediate file based on the path.
1178 defines = shlex.split(xcode_settings.GetPerTargetSetting(
1179 'INFOPLIST_PREPROCESSOR_DEFINITIONS', default=''))
1183 dest_plist = os.path.join(product_dir, xcode_settings.GetBundlePlistPath())
1184 extra_env = xcode_settings.GetPerTargetSettings()
1186 return info_plist, dest_plist, defines, extra_env
1189 def _GetXcodeEnv(xcode_settings, built_products_dir, srcroot, configuration,
1190 additional_settings=None):
1191 """Return the environment variables that Xcode would set. See
1192 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
1196 xcode_settings: An XcodeSettings object. If this is None, this function
1197 returns an empty dict.
1198 built_products_dir: Absolute path to the built products dir.
1199 srcroot: Absolute path to the source root.
1200 configuration: The build configuration name.
1201 additional_settings: An optional dict with more values to add to the
1204 if not xcode_settings: return {}
1206 # This function is considered a friend of XcodeSettings, so let it reach into
1207 # its implementation details.
1208 spec = xcode_settings.spec
1210 # These are filled in on a as-needed basis.
1212 'BUILT_PRODUCTS_DIR' : built_products_dir,
1213 'CONFIGURATION' : configuration,
1214 'PRODUCT_NAME' : xcode_settings.GetProductName(),
1215 # See /Developer/Platforms/MacOSX.platform/Developer/Library/Xcode/Specifications/MacOSX\ Product\ Types.xcspec for FULL_PRODUCT_NAME
1216 'SRCROOT' : srcroot,
1217 'SOURCE_ROOT': '${SRCROOT}',
1218 # This is not true for static libraries, but currently the env is only
1219 # written for bundles:
1220 'TARGET_BUILD_DIR' : built_products_dir,
1221 'TEMP_DIR' : '${TMPDIR}',
1223 if xcode_settings.GetPerConfigSetting('SDKROOT', configuration):
1224 env['SDKROOT'] = xcode_settings._SdkPath(configuration)
1228 if spec['type'] in (
1229 'executable', 'static_library', 'shared_library', 'loadable_module'):
1230 env['EXECUTABLE_NAME'] = xcode_settings.GetExecutableName()
1231 env['EXECUTABLE_PATH'] = xcode_settings.GetExecutablePath()
1232 env['FULL_PRODUCT_NAME'] = xcode_settings.GetFullProductName()
1233 mach_o_type = xcode_settings.GetMachOType()
1235 env['MACH_O_TYPE'] = mach_o_type
1236 env['PRODUCT_TYPE'] = xcode_settings.GetProductType()
1237 if xcode_settings._IsBundle():
1238 env['CONTENTS_FOLDER_PATH'] = \
1239 xcode_settings.GetBundleContentsFolderPath()
1240 env['UNLOCALIZED_RESOURCES_FOLDER_PATH'] = \
1241 xcode_settings.GetBundleResourceFolder()
1242 env['INFOPLIST_PATH'] = xcode_settings.GetBundlePlistPath()
1243 env['WRAPPER_NAME'] = xcode_settings.GetWrapperName()
1245 install_name = xcode_settings.GetInstallName()
1247 env['LD_DYLIB_INSTALL_NAME'] = install_name
1248 install_name_base = xcode_settings.GetInstallNameBase()
1249 if install_name_base:
1250 env['DYLIB_INSTALL_NAME_BASE'] = install_name_base
1252 if not additional_settings:
1253 additional_settings = {}
1255 # Flatten lists to strings.
1256 for k in additional_settings:
1257 if not isinstance(additional_settings[k], str):
1258 additional_settings[k] = ' '.join(additional_settings[k])
1259 additional_settings.update(env)
1261 for k in additional_settings:
1262 additional_settings[k] = _NormalizeEnvVarReferences(additional_settings[k])
1264 return additional_settings
1267 def _NormalizeEnvVarReferences(str):
1268 """Takes a string containing variable references in the form ${FOO}, $(FOO),
1269 or $FOO, and returns a string with all variable references in the form ${FOO}.
1272 str = re.sub(r'\$([a-zA-Z_][a-zA-Z0-9_]*)', r'${\1}', str)
1275 matches = re.findall(r'(\$\(([a-zA-Z0-9\-_]+)\))', str)
1276 for match in matches:
1277 to_replace, variable = match
1278 assert '$(' not in match, '$($(FOO)) variables not supported: ' + match
1279 str = str.replace(to_replace, '${' + variable + '}')
1284 def ExpandEnvVars(string, expansions):
1285 """Expands ${VARIABLES}, $(VARIABLES), and $VARIABLES in string per the
1286 expansions list. If the variable expands to something that references
1287 another variable, this variable is expanded as well if it's in env --
1288 until no variables present in env are left."""
1289 for k, v in reversed(expansions):
1290 string = string.replace('${' + k + '}', v)
1291 string = string.replace('$(' + k + ')', v)
1292 string = string.replace('$' + k, v)
1296 def _TopologicallySortedEnvVarKeys(env):
1297 """Takes a dict |env| whose values are strings that can refer to other keys,
1298 for example env['foo'] = '$(bar) and $(baz)'. Returns a list L of all keys of
1299 env such that key2 is after key1 in L if env[key2] refers to env[key1].
1301 Throws an Exception in case of dependency cycles.
1303 # Since environment variables can refer to other variables, the evaluation
1304 # order is important. Below is the logic to compute the dependency graph
1306 regex = re.compile(r'\$\{([a-zA-Z0-9\-_]+)\}')
1308 # Use a definition of edges such that user_of_variable -> used_varible.
1309 # This happens to be easier in this case, since a variable's
1310 # definition contains all variables it references in a single string.
1311 # We can then reverse the result of the topological sort at the end.
1312 # Since: reverse(topsort(DAG)) = topsort(reverse_edges(DAG))
1313 matches = set([v for v in regex.findall(env[node]) if v in env])
1314 for dependee in matches:
1315 assert '${' not in dependee, 'Nested variables not supported: ' + dependee
1319 # Topologically sort, and then reverse, because we used an edge definition
1320 # that's inverted from the expected result of this function (see comment
1322 order = gyp.common.TopologicallySorted(env.keys(), GetEdges)
1325 except gyp.common.CycleError, e:
1327 'Xcode environment variables are cyclically dependent: ' + str(e.nodes))
1330 def GetSortedXcodeEnv(xcode_settings, built_products_dir, srcroot,
1331 configuration, additional_settings=None):
1332 env = _GetXcodeEnv(xcode_settings, built_products_dir, srcroot, configuration,
1333 additional_settings)
1334 return [(key, env[key]) for key in _TopologicallySortedEnvVarKeys(env)]
1337 def GetSpecPostbuildCommands(spec, quiet=False):
1338 """Returns the list of postbuilds explicitly defined on |spec|, in a form
1339 executable by a shell."""
1341 for postbuild in spec.get('postbuilds', []):
1343 postbuilds.append('echo POSTBUILD\\(%s\\) %s' % (
1344 spec['target_name'], postbuild['postbuild_name']))
1345 postbuilds.append(gyp.common.EncodePOSIXShellList(postbuild['action']))
1349 def _HasIOSTarget(targets):
1350 """Returns true if any target contains the iOS specific key
1351 IPHONEOS_DEPLOYMENT_TARGET."""
1352 for target_dict in targets.values():
1353 for config in target_dict['configurations'].values():
1354 if config.get('xcode_settings', {}).get('IPHONEOS_DEPLOYMENT_TARGET'):
1359 def _AddIOSDeviceConfigurations(targets):
1360 """Clone all targets and append -iphoneos to the name. Configure these targets
1361 to build for iOS devices."""
1362 for target_dict in targets.values():
1363 for config_name in target_dict['configurations'].keys():
1364 config = target_dict['configurations'][config_name]
1365 new_config_name = config_name + '-iphoneos'
1366 new_config_dict = copy.deepcopy(config)
1367 if target_dict['toolset'] == 'target':
1368 new_config_dict['xcode_settings']['ARCHS'] = ['armv7']
1369 new_config_dict['xcode_settings']['SDKROOT'] = 'iphoneos'
1370 target_dict['configurations'][new_config_name] = new_config_dict
1373 def CloneConfigurationForDeviceAndEmulator(target_dicts):
1374 """If |target_dicts| contains any iOS targets, automatically create -iphoneos
1375 targets for iOS device builds."""
1376 if _HasIOSTarget(target_dicts):
1377 return _AddIOSDeviceConfigurations(target_dicts)