1# Copyright (c) 2012 Google Inc. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5""" 6This module contains classes that help to emulate xcodebuild behavior on top of 7other build systems, such as make and ninja. 8""" 9 10import copy 11import gyp.common 12import os 13import os.path 14import re 15import shlex 16import subprocess 17import sys 18import tempfile 19from gyp.common import GypError 20 21# Populated lazily by XcodeVersion, for efficiency, and to fix an issue when 22# "xcodebuild" is called too quickly (it has been found to return incorrect 23# version number). 24XCODE_VERSION_CACHE = None 25 26# Populated lazily by GetXcodeArchsDefault, to an |XcodeArchsDefault| instance 27# corresponding to the installed version of Xcode. 28XCODE_ARCHS_DEFAULT_CACHE = None 29 30 31def XcodeArchsVariableMapping(archs, archs_including_64_bit=None): 32 """Constructs a dictionary with expansion for $(ARCHS_STANDARD) variable, 33 and optionally for $(ARCHS_STANDARD_INCLUDING_64_BIT).""" 34 mapping = {'$(ARCHS_STANDARD)': archs} 35 if archs_including_64_bit: 36 mapping['$(ARCHS_STANDARD_INCLUDING_64_BIT)'] = archs_including_64_bit 37 return mapping 38 39class XcodeArchsDefault(object): 40 """A class to resolve ARCHS variable from xcode_settings, resolving Xcode 41 macros and implementing filtering by VALID_ARCHS. The expansion of macros 42 depends on the SDKROOT used ("macosx", "iphoneos", "iphonesimulator") and 43 on the version of Xcode. 44 """ 45 46 # Match variable like $(ARCHS_STANDARD). 47 variable_pattern = re.compile(r'\$\([a-zA-Z_][a-zA-Z0-9_]*\)$') 48 49 def __init__(self, default, mac, iphonesimulator, iphoneos): 50 self._default = (default,) 51 self._archs = {'mac': mac, 'ios': iphoneos, 'iossim': iphonesimulator} 52 53 def _VariableMapping(self, sdkroot): 54 """Returns the dictionary of variable mapping depending on the SDKROOT.""" 55 sdkroot = sdkroot.lower() 56 if 'iphoneos' in sdkroot: 57 return self._archs['ios'] 58 elif 'iphonesimulator' in sdkroot: 59 return self._archs['iossim'] 60 else: 61 return self._archs['mac'] 62 63 def _ExpandArchs(self, archs, sdkroot): 64 """Expands variables references in ARCHS, and remove duplicates.""" 65 variable_mapping = self._VariableMapping(sdkroot) 66 expanded_archs = [] 67 for arch in archs: 68 if self.variable_pattern.match(arch): 69 variable = arch 70 try: 71 variable_expansion = variable_mapping[variable] 72 for arch in variable_expansion: 73 if arch not in expanded_archs: 74 expanded_archs.append(arch) 75 except KeyError as e: 76 print 'Warning: Ignoring unsupported variable "%s".' % variable 77 elif arch not in expanded_archs: 78 expanded_archs.append(arch) 79 return expanded_archs 80 81 def ActiveArchs(self, archs, valid_archs, sdkroot): 82 """Expands variables references in ARCHS, and filter by VALID_ARCHS if it 83 is defined (if not set, Xcode accept any value in ARCHS, otherwise, only 84 values present in VALID_ARCHS are kept).""" 85 expanded_archs = self._ExpandArchs(archs or self._default, sdkroot or '') 86 if valid_archs: 87 filtered_archs = [] 88 for arch in expanded_archs: 89 if arch in valid_archs: 90 filtered_archs.append(arch) 91 expanded_archs = filtered_archs 92 return expanded_archs 93 94 95def GetXcodeArchsDefault(): 96 """Returns the |XcodeArchsDefault| object to use to expand ARCHS for the 97 installed version of Xcode. The default values used by Xcode for ARCHS 98 and the expansion of the variables depends on the version of Xcode used. 99 100 For all version anterior to Xcode 5.0 or posterior to Xcode 5.1 included 101 uses $(ARCHS_STANDARD) if ARCHS is unset, while Xcode 5.0 to 5.0.2 uses 102 $(ARCHS_STANDARD_INCLUDING_64_BIT). This variable was added to Xcode 5.0 103 and deprecated with Xcode 5.1. 104 105 For "macosx" SDKROOT, all version starting with Xcode 5.0 includes 64-bit 106 architecture as part of $(ARCHS_STANDARD) and default to only building it. 107 108 For "iphoneos" and "iphonesimulator" SDKROOT, 64-bit architectures are part 109 of $(ARCHS_STANDARD_INCLUDING_64_BIT) from Xcode 5.0. From Xcode 5.1, they 110 are also part of $(ARCHS_STANDARD). 111 112 All thoses rules are coded in the construction of the |XcodeArchsDefault| 113 object to use depending on the version of Xcode detected. The object is 114 for performance reason.""" 115 global XCODE_ARCHS_DEFAULT_CACHE 116 if XCODE_ARCHS_DEFAULT_CACHE: 117 return XCODE_ARCHS_DEFAULT_CACHE 118 xcode_version, _ = XcodeVersion() 119 if xcode_version < '0500': 120 XCODE_ARCHS_DEFAULT_CACHE = XcodeArchsDefault( 121 '$(ARCHS_STANDARD)', 122 XcodeArchsVariableMapping(['i386']), 123 XcodeArchsVariableMapping(['i386']), 124 XcodeArchsVariableMapping(['armv7'])) 125 elif xcode_version < '0510': 126 XCODE_ARCHS_DEFAULT_CACHE = XcodeArchsDefault( 127 '$(ARCHS_STANDARD_INCLUDING_64_BIT)', 128 XcodeArchsVariableMapping(['x86_64'], ['x86_64']), 129 XcodeArchsVariableMapping(['i386'], ['i386', 'x86_64']), 130 XcodeArchsVariableMapping( 131 ['armv7', 'armv7s'], 132 ['armv7', 'armv7s', 'arm64'])) 133 else: 134 XCODE_ARCHS_DEFAULT_CACHE = XcodeArchsDefault( 135 '$(ARCHS_STANDARD)', 136 XcodeArchsVariableMapping(['x86_64'], ['x86_64']), 137 XcodeArchsVariableMapping(['i386', 'x86_64'], ['i386', 'x86_64']), 138 XcodeArchsVariableMapping( 139 ['armv7', 'armv7s', 'arm64'], 140 ['armv7', 'armv7s', 'arm64'])) 141 return XCODE_ARCHS_DEFAULT_CACHE 142 143 144class XcodeSettings(object): 145 """A class that understands the gyp 'xcode_settings' object.""" 146 147 # Populated lazily by _SdkPath(). Shared by all XcodeSettings, so cached 148 # at class-level for efficiency. 149 _sdk_path_cache = {} 150 _sdk_root_cache = {} 151 152 # Populated lazily by GetExtraPlistItems(). Shared by all XcodeSettings, so 153 # cached at class-level for efficiency. 154 _plist_cache = {} 155 156 # Populated lazily by GetIOSPostbuilds. Shared by all XcodeSettings, so 157 # cached at class-level for efficiency. 158 _codesigning_key_cache = {} 159 160 def __init__(self, spec): 161 self.spec = spec 162 163 self.isIOS = False 164 165 # Per-target 'xcode_settings' are pushed down into configs earlier by gyp. 166 # This means self.xcode_settings[config] always contains all settings 167 # for that config -- the per-target settings as well. Settings that are 168 # the same for all configs are implicitly per-target settings. 169 self.xcode_settings = {} 170 configs = spec['configurations'] 171 for configname, config in configs.iteritems(): 172 self.xcode_settings[configname] = config.get('xcode_settings', {}) 173 self._ConvertConditionalKeys(configname) 174 if self.xcode_settings[configname].get('IPHONEOS_DEPLOYMENT_TARGET', 175 None): 176 self.isIOS = True 177 178 # This is only non-None temporarily during the execution of some methods. 179 self.configname = None 180 181 # Used by _AdjustLibrary to match .a and .dylib entries in libraries. 182 self.library_re = re.compile(r'^lib([^/]+)\.(a|dylib)$') 183 184 def _ConvertConditionalKeys(self, configname): 185 """Converts or warns on conditional keys. Xcode supports conditional keys, 186 such as CODE_SIGN_IDENTITY[sdk=iphoneos*]. This is a partial implementation 187 with some keys converted while the rest force a warning.""" 188 settings = self.xcode_settings[configname] 189 conditional_keys = [key for key in settings if key.endswith(']')] 190 for key in conditional_keys: 191 # If you need more, speak up at http://crbug.com/122592 192 if key.endswith("[sdk=iphoneos*]"): 193 if configname.endswith("iphoneos"): 194 new_key = key.split("[")[0] 195 settings[new_key] = settings[key] 196 else: 197 print 'Warning: Conditional keys not implemented, ignoring:', \ 198 ' '.join(conditional_keys) 199 del settings[key] 200 201 def _Settings(self): 202 assert self.configname 203 return self.xcode_settings[self.configname] 204 205 def _Test(self, test_key, cond_key, default): 206 return self._Settings().get(test_key, default) == cond_key 207 208 def _Appendf(self, lst, test_key, format_str, default=None): 209 if test_key in self._Settings(): 210 lst.append(format_str % str(self._Settings()[test_key])) 211 elif default: 212 lst.append(format_str % str(default)) 213 214 def _WarnUnimplemented(self, test_key): 215 if test_key in self._Settings(): 216 print 'Warning: Ignoring not yet implemented key "%s".' % test_key 217 218 def _IsBundle(self): 219 return int(self.spec.get('mac_bundle', 0)) != 0 220 221 def GetFrameworkVersion(self): 222 """Returns the framework version of the current target. Only valid for 223 bundles.""" 224 assert self._IsBundle() 225 return self.GetPerTargetSetting('FRAMEWORK_VERSION', default='A') 226 227 def GetWrapperExtension(self): 228 """Returns the bundle extension (.app, .framework, .plugin, etc). Only 229 valid for bundles.""" 230 assert self._IsBundle() 231 if self.spec['type'] in ('loadable_module', 'shared_library'): 232 default_wrapper_extension = { 233 'loadable_module': 'bundle', 234 'shared_library': 'framework', 235 }[self.spec['type']] 236 wrapper_extension = self.GetPerTargetSetting( 237 'WRAPPER_EXTENSION', default=default_wrapper_extension) 238 return '.' + self.spec.get('product_extension', wrapper_extension) 239 elif self.spec['type'] == 'executable': 240 return '.' + self.spec.get('product_extension', 'app') 241 else: 242 assert False, "Don't know extension for '%s', target '%s'" % ( 243 self.spec['type'], self.spec['target_name']) 244 245 def GetProductName(self): 246 """Returns PRODUCT_NAME.""" 247 return self.spec.get('product_name', self.spec['target_name']) 248 249 def GetFullProductName(self): 250 """Returns FULL_PRODUCT_NAME.""" 251 if self._IsBundle(): 252 return self.GetWrapperName() 253 else: 254 return self._GetStandaloneBinaryPath() 255 256 def GetWrapperName(self): 257 """Returns the directory name of the bundle represented by this target. 258 Only valid for bundles.""" 259 assert self._IsBundle() 260 return self.GetProductName() + self.GetWrapperExtension() 261 262 def GetBundleContentsFolderPath(self): 263 """Returns the qualified path to the bundle's contents folder. E.g. 264 Chromium.app/Contents or Foo.bundle/Versions/A. Only valid for bundles.""" 265 if self.isIOS: 266 return self.GetWrapperName() 267 assert self._IsBundle() 268 if self.spec['type'] == 'shared_library': 269 return os.path.join( 270 self.GetWrapperName(), 'Versions', self.GetFrameworkVersion()) 271 else: 272 # loadable_modules have a 'Contents' folder like executables. 273 return os.path.join(self.GetWrapperName(), 'Contents') 274 275 def GetBundleResourceFolder(self): 276 """Returns the qualified path to the bundle's resource folder. E.g. 277 Chromium.app/Contents/Resources. Only valid for bundles.""" 278 assert self._IsBundle() 279 if self.isIOS: 280 return self.GetBundleContentsFolderPath() 281 return os.path.join(self.GetBundleContentsFolderPath(), 'Resources') 282 283 def GetBundlePlistPath(self): 284 """Returns the qualified path to the bundle's plist file. E.g. 285 Chromium.app/Contents/Info.plist. Only valid for bundles.""" 286 assert self._IsBundle() 287 if self.spec['type'] in ('executable', 'loadable_module'): 288 return os.path.join(self.GetBundleContentsFolderPath(), 'Info.plist') 289 else: 290 return os.path.join(self.GetBundleContentsFolderPath(), 291 'Resources', 'Info.plist') 292 293 def GetProductType(self): 294 """Returns the PRODUCT_TYPE of this target.""" 295 if self._IsBundle(): 296 return { 297 'executable': 'com.apple.product-type.application', 298 'loadable_module': 'com.apple.product-type.bundle', 299 'shared_library': 'com.apple.product-type.framework', 300 }[self.spec['type']] 301 else: 302 return { 303 'executable': 'com.apple.product-type.tool', 304 'loadable_module': 'com.apple.product-type.library.dynamic', 305 'shared_library': 'com.apple.product-type.library.dynamic', 306 'static_library': 'com.apple.product-type.library.static', 307 }[self.spec['type']] 308 309 def GetMachOType(self): 310 """Returns the MACH_O_TYPE of this target.""" 311 # Weird, but matches Xcode. 312 if not self._IsBundle() and self.spec['type'] == 'executable': 313 return '' 314 return { 315 'executable': 'mh_execute', 316 'static_library': 'staticlib', 317 'shared_library': 'mh_dylib', 318 'loadable_module': 'mh_bundle', 319 }[self.spec['type']] 320 321 def _GetBundleBinaryPath(self): 322 """Returns the name of the bundle binary of by this target. 323 E.g. Chromium.app/Contents/MacOS/Chromium. Only valid for bundles.""" 324 assert self._IsBundle() 325 if self.spec['type'] in ('shared_library') or self.isIOS: 326 path = self.GetBundleContentsFolderPath() 327 elif self.spec['type'] in ('executable', 'loadable_module'): 328 path = os.path.join(self.GetBundleContentsFolderPath(), 'MacOS') 329 return os.path.join(path, self.GetExecutableName()) 330 331 def _GetStandaloneExecutableSuffix(self): 332 if 'product_extension' in self.spec: 333 return '.' + self.spec['product_extension'] 334 return { 335 'executable': '', 336 'static_library': '.a', 337 'shared_library': '.dylib', 338 'loadable_module': '.so', 339 }[self.spec['type']] 340 341 def _GetStandaloneExecutablePrefix(self): 342 return self.spec.get('product_prefix', { 343 'executable': '', 344 'static_library': 'lib', 345 'shared_library': 'lib', 346 # Non-bundled loadable_modules are called foo.so for some reason 347 # (that is, .so and no prefix) with the xcode build -- match that. 348 'loadable_module': '', 349 }[self.spec['type']]) 350 351 def _GetStandaloneBinaryPath(self): 352 """Returns the name of the non-bundle binary represented by this target. 353 E.g. hello_world. Only valid for non-bundles.""" 354 assert not self._IsBundle() 355 assert self.spec['type'] in ( 356 'executable', 'shared_library', 'static_library', 'loadable_module'), ( 357 'Unexpected type %s' % self.spec['type']) 358 target = self.spec['target_name'] 359 if self.spec['type'] == 'static_library': 360 if target[:3] == 'lib': 361 target = target[3:] 362 elif self.spec['type'] in ('loadable_module', 'shared_library'): 363 if target[:3] == 'lib': 364 target = target[3:] 365 366 target_prefix = self._GetStandaloneExecutablePrefix() 367 target = self.spec.get('product_name', target) 368 target_ext = self._GetStandaloneExecutableSuffix() 369 return target_prefix + target + target_ext 370 371 def GetExecutableName(self): 372 """Returns the executable name of the bundle represented by this target. 373 E.g. Chromium.""" 374 if self._IsBundle(): 375 return self.spec.get('product_name', self.spec['target_name']) 376 else: 377 return self._GetStandaloneBinaryPath() 378 379 def GetExecutablePath(self): 380 """Returns the directory name of the bundle represented by this target. E.g. 381 Chromium.app/Contents/MacOS/Chromium.""" 382 if self._IsBundle(): 383 return self._GetBundleBinaryPath() 384 else: 385 return self._GetStandaloneBinaryPath() 386 387 def GetActiveArchs(self, configname): 388 """Returns the architectures this target should be built for.""" 389 config_settings = self.xcode_settings[configname] 390 xcode_archs_default = GetXcodeArchsDefault() 391 return xcode_archs_default.ActiveArchs( 392 config_settings.get('ARCHS'), 393 config_settings.get('VALID_ARCHS'), 394 config_settings.get('SDKROOT')) 395 396 def _GetSdkVersionInfoItem(self, sdk, infoitem): 397 # xcodebuild requires Xcode and can't run on Command Line Tools-only 398 # systems from 10.7 onward. 399 # Since the CLT has no SDK paths anyway, returning None is the 400 # most sensible route and should still do the right thing. 401 try: 402 return GetStdout(['xcodebuild', '-version', '-sdk', sdk, infoitem]) 403 except: 404 pass 405 406 def _SdkRoot(self, configname): 407 if configname is None: 408 configname = self.configname 409 return self.GetPerConfigSetting('SDKROOT', configname, default='') 410 411 def _SdkPath(self, configname=None): 412 sdk_root = self._SdkRoot(configname) 413 if sdk_root.startswith('/'): 414 return sdk_root 415 return self._XcodeSdkPath(sdk_root) 416 417 def _XcodeSdkPath(self, sdk_root): 418 if sdk_root not in XcodeSettings._sdk_path_cache: 419 sdk_path = self._GetSdkVersionInfoItem(sdk_root, 'Path') 420 XcodeSettings._sdk_path_cache[sdk_root] = sdk_path 421 if sdk_root: 422 XcodeSettings._sdk_root_cache[sdk_path] = sdk_root 423 return XcodeSettings._sdk_path_cache[sdk_root] 424 425 def _AppendPlatformVersionMinFlags(self, lst): 426 self._Appendf(lst, 'MACOSX_DEPLOYMENT_TARGET', '-mmacosx-version-min=%s') 427 if 'IPHONEOS_DEPLOYMENT_TARGET' in self._Settings(): 428 # TODO: Implement this better? 429 sdk_path_basename = os.path.basename(self._SdkPath()) 430 if sdk_path_basename.lower().startswith('iphonesimulator'): 431 self._Appendf(lst, 'IPHONEOS_DEPLOYMENT_TARGET', 432 '-mios-simulator-version-min=%s') 433 else: 434 self._Appendf(lst, 'IPHONEOS_DEPLOYMENT_TARGET', 435 '-miphoneos-version-min=%s') 436 437 def GetCflags(self, configname, arch=None): 438 """Returns flags that need to be added to .c, .cc, .m, and .mm 439 compilations.""" 440 # This functions (and the similar ones below) do not offer complete 441 # emulation of all xcode_settings keys. They're implemented on demand. 442 443 self.configname = configname 444 cflags = [] 445 446 sdk_root = self._SdkPath() 447 if 'SDKROOT' in self._Settings() and sdk_root: 448 cflags.append('-isysroot %s' % sdk_root) 449 450 if self._Test('CLANG_WARN_CONSTANT_CONVERSION', 'YES', default='NO'): 451 cflags.append('-Wconstant-conversion') 452 453 if self._Test('GCC_CHAR_IS_UNSIGNED_CHAR', 'YES', default='NO'): 454 cflags.append('-funsigned-char') 455 456 if self._Test('GCC_CW_ASM_SYNTAX', 'YES', default='YES'): 457 cflags.append('-fasm-blocks') 458 459 if 'GCC_DYNAMIC_NO_PIC' in self._Settings(): 460 if self._Settings()['GCC_DYNAMIC_NO_PIC'] == 'YES': 461 cflags.append('-mdynamic-no-pic') 462 else: 463 pass 464 # TODO: In this case, it depends on the target. xcode passes 465 # mdynamic-no-pic by default for executable and possibly static lib 466 # according to mento 467 468 if self._Test('GCC_ENABLE_PASCAL_STRINGS', 'YES', default='YES'): 469 cflags.append('-mpascal-strings') 470 471 self._Appendf(cflags, 'GCC_OPTIMIZATION_LEVEL', '-O%s', default='s') 472 473 if self._Test('GCC_GENERATE_DEBUGGING_SYMBOLS', 'YES', default='YES'): 474 dbg_format = self._Settings().get('DEBUG_INFORMATION_FORMAT', 'dwarf') 475 if dbg_format == 'dwarf': 476 cflags.append('-gdwarf-2') 477 elif dbg_format == 'stabs': 478 raise NotImplementedError('stabs debug format is not supported yet.') 479 elif dbg_format == 'dwarf-with-dsym': 480 cflags.append('-gdwarf-2') 481 else: 482 raise NotImplementedError('Unknown debug format %s' % dbg_format) 483 484 if self._Settings().get('GCC_STRICT_ALIASING') == 'YES': 485 cflags.append('-fstrict-aliasing') 486 elif self._Settings().get('GCC_STRICT_ALIASING') == 'NO': 487 cflags.append('-fno-strict-aliasing') 488 489 if self._Test('GCC_SYMBOLS_PRIVATE_EXTERN', 'YES', default='NO'): 490 cflags.append('-fvisibility=hidden') 491 492 if self._Test('GCC_TREAT_WARNINGS_AS_ERRORS', 'YES', default='NO'): 493 cflags.append('-Werror') 494 495 if self._Test('GCC_WARN_ABOUT_MISSING_NEWLINE', 'YES', default='NO'): 496 cflags.append('-Wnewline-eof') 497 498 self._AppendPlatformVersionMinFlags(cflags) 499 500 # TODO: 501 if self._Test('COPY_PHASE_STRIP', 'YES', default='NO'): 502 self._WarnUnimplemented('COPY_PHASE_STRIP') 503 self._WarnUnimplemented('GCC_DEBUGGING_SYMBOLS') 504 self._WarnUnimplemented('GCC_ENABLE_OBJC_EXCEPTIONS') 505 506 # TODO: This is exported correctly, but assigning to it is not supported. 507 self._WarnUnimplemented('MACH_O_TYPE') 508 self._WarnUnimplemented('PRODUCT_TYPE') 509 510 if arch is not None: 511 archs = [arch] 512 else: 513 assert self.configname 514 archs = self.GetActiveArchs(self.configname) 515 if len(archs) != 1: 516 # TODO: Supporting fat binaries will be annoying. 517 self._WarnUnimplemented('ARCHS') 518 archs = ['i386'] 519 cflags.append('-arch ' + archs[0]) 520 521 if archs[0] in ('i386', 'x86_64'): 522 if self._Test('GCC_ENABLE_SSE3_EXTENSIONS', 'YES', default='NO'): 523 cflags.append('-msse3') 524 if self._Test('GCC_ENABLE_SUPPLEMENTAL_SSE3_INSTRUCTIONS', 'YES', 525 default='NO'): 526 cflags.append('-mssse3') # Note 3rd 's'. 527 if self._Test('GCC_ENABLE_SSE41_EXTENSIONS', 'YES', default='NO'): 528 cflags.append('-msse4.1') 529 if self._Test('GCC_ENABLE_SSE42_EXTENSIONS', 'YES', default='NO'): 530 cflags.append('-msse4.2') 531 532 cflags += self._Settings().get('WARNING_CFLAGS', []) 533 534 if sdk_root: 535 framework_root = sdk_root 536 else: 537 framework_root = '' 538 config = self.spec['configurations'][self.configname] 539 framework_dirs = config.get('mac_framework_dirs', []) 540 for directory in framework_dirs: 541 cflags.append('-F' + directory.replace('$(SDKROOT)', framework_root)) 542 543 self.configname = None 544 return cflags 545 546 def GetCflagsC(self, configname): 547 """Returns flags that need to be added to .c, and .m compilations.""" 548 self.configname = configname 549 cflags_c = [] 550 if self._Settings().get('GCC_C_LANGUAGE_STANDARD', '') == 'ansi': 551 cflags_c.append('-ansi') 552 else: 553 self._Appendf(cflags_c, 'GCC_C_LANGUAGE_STANDARD', '-std=%s') 554 cflags_c += self._Settings().get('OTHER_CFLAGS', []) 555 self.configname = None 556 return cflags_c 557 558 def GetCflagsCC(self, configname): 559 """Returns flags that need to be added to .cc, and .mm compilations.""" 560 self.configname = configname 561 cflags_cc = [] 562 563 clang_cxx_language_standard = self._Settings().get( 564 'CLANG_CXX_LANGUAGE_STANDARD') 565 # Note: Don't make c++0x to c++11 so that c++0x can be used with older 566 # clangs that don't understand c++11 yet (like Xcode 4.2's). 567 if clang_cxx_language_standard: 568 cflags_cc.append('-std=%s' % clang_cxx_language_standard) 569 570 self._Appendf(cflags_cc, 'CLANG_CXX_LIBRARY', '-stdlib=%s') 571 572 if self._Test('GCC_ENABLE_CPP_RTTI', 'NO', default='YES'): 573 cflags_cc.append('-fno-rtti') 574 if self._Test('GCC_ENABLE_CPP_EXCEPTIONS', 'NO', default='YES'): 575 cflags_cc.append('-fno-exceptions') 576 if self._Test('GCC_INLINES_ARE_PRIVATE_EXTERN', 'YES', default='NO'): 577 cflags_cc.append('-fvisibility-inlines-hidden') 578 if self._Test('GCC_THREADSAFE_STATICS', 'NO', default='YES'): 579 cflags_cc.append('-fno-threadsafe-statics') 580 # Note: This flag is a no-op for clang, it only has an effect for gcc. 581 if self._Test('GCC_WARN_ABOUT_INVALID_OFFSETOF_MACRO', 'NO', default='YES'): 582 cflags_cc.append('-Wno-invalid-offsetof') 583 584 other_ccflags = [] 585 586 for flag in self._Settings().get('OTHER_CPLUSPLUSFLAGS', ['$(inherited)']): 587 # TODO: More general variable expansion. Missing in many other places too. 588 if flag in ('$inherited', '$(inherited)', '${inherited}'): 589 flag = '$OTHER_CFLAGS' 590 if flag in ('$OTHER_CFLAGS', '$(OTHER_CFLAGS)', '${OTHER_CFLAGS}'): 591 other_ccflags += self._Settings().get('OTHER_CFLAGS', []) 592 else: 593 other_ccflags.append(flag) 594 cflags_cc += other_ccflags 595 596 self.configname = None 597 return cflags_cc 598 599 def _AddObjectiveCGarbageCollectionFlags(self, flags): 600 gc_policy = self._Settings().get('GCC_ENABLE_OBJC_GC', 'unsupported') 601 if gc_policy == 'supported': 602 flags.append('-fobjc-gc') 603 elif gc_policy == 'required': 604 flags.append('-fobjc-gc-only') 605 606 def _AddObjectiveCARCFlags(self, flags): 607 if self._Test('CLANG_ENABLE_OBJC_ARC', 'YES', default='NO'): 608 flags.append('-fobjc-arc') 609 610 def _AddObjectiveCMissingPropertySynthesisFlags(self, flags): 611 if self._Test('CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS', 612 'YES', default='NO'): 613 flags.append('-Wobjc-missing-property-synthesis') 614 615 def GetCflagsObjC(self, configname): 616 """Returns flags that need to be added to .m compilations.""" 617 self.configname = configname 618 cflags_objc = [] 619 self._AddObjectiveCGarbageCollectionFlags(cflags_objc) 620 self._AddObjectiveCARCFlags(cflags_objc) 621 self._AddObjectiveCMissingPropertySynthesisFlags(cflags_objc) 622 self.configname = None 623 return cflags_objc 624 625 def GetCflagsObjCC(self, configname): 626 """Returns flags that need to be added to .mm compilations.""" 627 self.configname = configname 628 cflags_objcc = [] 629 self._AddObjectiveCGarbageCollectionFlags(cflags_objcc) 630 self._AddObjectiveCARCFlags(cflags_objcc) 631 self._AddObjectiveCMissingPropertySynthesisFlags(cflags_objcc) 632 if self._Test('GCC_OBJC_CALL_CXX_CDTORS', 'YES', default='NO'): 633 cflags_objcc.append('-fobjc-call-cxx-cdtors') 634 self.configname = None 635 return cflags_objcc 636 637 def GetInstallNameBase(self): 638 """Return DYLIB_INSTALL_NAME_BASE for this target.""" 639 # Xcode sets this for shared_libraries, and for nonbundled loadable_modules. 640 if (self.spec['type'] != 'shared_library' and 641 (self.spec['type'] != 'loadable_module' or self._IsBundle())): 642 return None 643 install_base = self.GetPerTargetSetting( 644 'DYLIB_INSTALL_NAME_BASE', 645 default='/Library/Frameworks' if self._IsBundle() else '/usr/local/lib') 646 return install_base 647 648 def _StandardizePath(self, path): 649 """Do :standardizepath processing for path.""" 650 # I'm not quite sure what :standardizepath does. Just call normpath(), 651 # but don't let @executable_path/../foo collapse to foo. 652 if '/' in path: 653 prefix, rest = '', path 654 if path.startswith('@'): 655 prefix, rest = path.split('/', 1) 656 rest = os.path.normpath(rest) # :standardizepath 657 path = os.path.join(prefix, rest) 658 return path 659 660 def GetInstallName(self): 661 """Return LD_DYLIB_INSTALL_NAME for this target.""" 662 # Xcode sets this for shared_libraries, and for nonbundled loadable_modules. 663 if (self.spec['type'] != 'shared_library' and 664 (self.spec['type'] != 'loadable_module' or self._IsBundle())): 665 return None 666 667 default_install_name = \ 668 '$(DYLIB_INSTALL_NAME_BASE:standardizepath)/$(EXECUTABLE_PATH)' 669 install_name = self.GetPerTargetSetting( 670 'LD_DYLIB_INSTALL_NAME', default=default_install_name) 671 672 # Hardcode support for the variables used in chromium for now, to 673 # unblock people using the make build. 674 if '$' in install_name: 675 assert install_name in ('$(DYLIB_INSTALL_NAME_BASE:standardizepath)/' 676 '$(WRAPPER_NAME)/$(PRODUCT_NAME)', default_install_name), ( 677 'Variables in LD_DYLIB_INSTALL_NAME are not generally supported ' 678 'yet in target \'%s\' (got \'%s\')' % 679 (self.spec['target_name'], install_name)) 680 681 install_name = install_name.replace( 682 '$(DYLIB_INSTALL_NAME_BASE:standardizepath)', 683 self._StandardizePath(self.GetInstallNameBase())) 684 if self._IsBundle(): 685 # These are only valid for bundles, hence the |if|. 686 install_name = install_name.replace( 687 '$(WRAPPER_NAME)', self.GetWrapperName()) 688 install_name = install_name.replace( 689 '$(PRODUCT_NAME)', self.GetProductName()) 690 else: 691 assert '$(WRAPPER_NAME)' not in install_name 692 assert '$(PRODUCT_NAME)' not in install_name 693 694 install_name = install_name.replace( 695 '$(EXECUTABLE_PATH)', self.GetExecutablePath()) 696 return install_name 697 698 def _MapLinkerFlagFilename(self, ldflag, gyp_to_build_path): 699 """Checks if ldflag contains a filename and if so remaps it from 700 gyp-directory-relative to build-directory-relative.""" 701 # This list is expanded on demand. 702 # They get matched as: 703 # -exported_symbols_list file 704 # -Wl,exported_symbols_list file 705 # -Wl,exported_symbols_list,file 706 LINKER_FILE = '(\S+)' 707 WORD = '\S+' 708 linker_flags = [ 709 ['-exported_symbols_list', LINKER_FILE], # Needed for NaCl. 710 ['-unexported_symbols_list', LINKER_FILE], 711 ['-reexported_symbols_list', LINKER_FILE], 712 ['-sectcreate', WORD, WORD, LINKER_FILE], # Needed for remoting. 713 ] 714 for flag_pattern in linker_flags: 715 regex = re.compile('(?:-Wl,)?' + '[ ,]'.join(flag_pattern)) 716 m = regex.match(ldflag) 717 if m: 718 ldflag = ldflag[:m.start(1)] + gyp_to_build_path(m.group(1)) + \ 719 ldflag[m.end(1):] 720 # Required for ffmpeg (no idea why they don't use LIBRARY_SEARCH_PATHS, 721 # TODO(thakis): Update ffmpeg.gyp): 722 if ldflag.startswith('-L'): 723 ldflag = '-L' + gyp_to_build_path(ldflag[len('-L'):]) 724 return ldflag 725 726 def GetLdflags(self, configname, product_dir, gyp_to_build_path, arch=None): 727 """Returns flags that need to be passed to the linker. 728 729 Args: 730 configname: The name of the configuration to get ld flags for. 731 product_dir: The directory where products such static and dynamic 732 libraries are placed. This is added to the library search path. 733 gyp_to_build_path: A function that converts paths relative to the 734 current gyp file to paths relative to the build direcotry. 735 """ 736 self.configname = configname 737 ldflags = [] 738 739 # The xcode build is relative to a gyp file's directory, and OTHER_LDFLAGS 740 # can contain entries that depend on this. Explicitly absolutify these. 741 for ldflag in self._Settings().get('OTHER_LDFLAGS', []): 742 ldflags.append(self._MapLinkerFlagFilename(ldflag, gyp_to_build_path)) 743 744 if self._Test('DEAD_CODE_STRIPPING', 'YES', default='NO'): 745 ldflags.append('-Wl,-dead_strip') 746 747 if self._Test('PREBINDING', 'YES', default='NO'): 748 ldflags.append('-Wl,-prebind') 749 750 self._Appendf( 751 ldflags, 'DYLIB_COMPATIBILITY_VERSION', '-compatibility_version %s') 752 self._Appendf( 753 ldflags, 'DYLIB_CURRENT_VERSION', '-current_version %s') 754 755 self._AppendPlatformVersionMinFlags(ldflags) 756 757 if 'SDKROOT' in self._Settings() and self._SdkPath(): 758 ldflags.append('-isysroot ' + self._SdkPath()) 759 760 for library_path in self._Settings().get('LIBRARY_SEARCH_PATHS', []): 761 ldflags.append('-L' + gyp_to_build_path(library_path)) 762 763 if 'ORDER_FILE' in self._Settings(): 764 ldflags.append('-Wl,-order_file ' + 765 '-Wl,' + gyp_to_build_path( 766 self._Settings()['ORDER_FILE'])) 767 768 if arch is not None: 769 archs = [arch] 770 else: 771 assert self.configname 772 archs = self.GetActiveArchs(self.configname) 773 if len(archs) != 1: 774 # TODO: Supporting fat binaries will be annoying. 775 self._WarnUnimplemented('ARCHS') 776 archs = ['i386'] 777 ldflags.append('-arch ' + archs[0]) 778 779 # Xcode adds the product directory by default. 780 ldflags.append('-L' + product_dir) 781 782 install_name = self.GetInstallName() 783 if install_name and self.spec['type'] != 'loadable_module': 784 ldflags.append('-install_name ' + install_name.replace(' ', r'\ ')) 785 786 for rpath in self._Settings().get('LD_RUNPATH_SEARCH_PATHS', []): 787 ldflags.append('-Wl,-rpath,' + rpath) 788 789 sdk_root = self._SdkPath() 790 if not sdk_root: 791 sdk_root = '' 792 config = self.spec['configurations'][self.configname] 793 framework_dirs = config.get('mac_framework_dirs', []) 794 for directory in framework_dirs: 795 ldflags.append('-F' + directory.replace('$(SDKROOT)', sdk_root)) 796 797 self._Appendf(ldflags, 'CLANG_CXX_LIBRARY', '-stdlib=%s') 798 799 self.configname = None 800 return ldflags 801 802 def GetLibtoolflags(self, configname): 803 """Returns flags that need to be passed to the static linker. 804 805 Args: 806 configname: The name of the configuration to get ld flags for. 807 """ 808 self.configname = configname 809 libtoolflags = [] 810 811 for libtoolflag in self._Settings().get('OTHER_LDFLAGS', []): 812 libtoolflags.append(libtoolflag) 813 # TODO(thakis): ARCHS? 814 815 self.configname = None 816 return libtoolflags 817 818 def GetPerTargetSettings(self): 819 """Gets a list of all the per-target settings. This will only fetch keys 820 whose values are the same across all configurations.""" 821 first_pass = True 822 result = {} 823 for configname in sorted(self.xcode_settings.keys()): 824 if first_pass: 825 result = dict(self.xcode_settings[configname]) 826 first_pass = False 827 else: 828 for key, value in self.xcode_settings[configname].iteritems(): 829 if key not in result: 830 continue 831 elif result[key] != value: 832 del result[key] 833 return result 834 835 def GetPerConfigSetting(self, setting, configname, default=None): 836 if configname in self.xcode_settings: 837 return self.xcode_settings[configname].get(setting, default) 838 else: 839 return self.GetPerTargetSetting(setting, default) 840 841 def GetPerTargetSetting(self, setting, default=None): 842 """Tries to get xcode_settings.setting from spec. Assumes that the setting 843 has the same value in all configurations and throws otherwise.""" 844 is_first_pass = True 845 result = None 846 for configname in sorted(self.xcode_settings.keys()): 847 if is_first_pass: 848 result = self.xcode_settings[configname].get(setting, None) 849 is_first_pass = False 850 else: 851 assert result == self.xcode_settings[configname].get(setting, None), ( 852 "Expected per-target setting for '%s', got per-config setting " 853 "(target %s)" % (setting, self.spec['target_name'])) 854 if result is None: 855 return default 856 return result 857 858 def _GetStripPostbuilds(self, configname, output_binary, quiet): 859 """Returns a list of shell commands that contain the shell commands 860 neccessary to strip this target's binary. These should be run as postbuilds 861 before the actual postbuilds run.""" 862 self.configname = configname 863 864 result = [] 865 if (self._Test('DEPLOYMENT_POSTPROCESSING', 'YES', default='NO') and 866 self._Test('STRIP_INSTALLED_PRODUCT', 'YES', default='NO')): 867 868 default_strip_style = 'debugging' 869 if self.spec['type'] == 'loadable_module' and self._IsBundle(): 870 default_strip_style = 'non-global' 871 elif self.spec['type'] == 'executable': 872 default_strip_style = 'all' 873 874 strip_style = self._Settings().get('STRIP_STYLE', default_strip_style) 875 strip_flags = { 876 'all': '', 877 'non-global': '-x', 878 'debugging': '-S', 879 }[strip_style] 880 881 explicit_strip_flags = self._Settings().get('STRIPFLAGS', '') 882 if explicit_strip_flags: 883 strip_flags += ' ' + _NormalizeEnvVarReferences(explicit_strip_flags) 884 885 if not quiet: 886 result.append('echo STRIP\\(%s\\)' % self.spec['target_name']) 887 result.append('strip %s %s' % (strip_flags, output_binary)) 888 889 self.configname = None 890 return result 891 892 def _GetDebugInfoPostbuilds(self, configname, output, output_binary, quiet): 893 """Returns a list of shell commands that contain the shell commands 894 neccessary to massage this target's debug information. These should be run 895 as postbuilds before the actual postbuilds run.""" 896 self.configname = configname 897 898 # For static libraries, no dSYMs are created. 899 result = [] 900 if (self._Test('GCC_GENERATE_DEBUGGING_SYMBOLS', 'YES', default='YES') and 901 self._Test( 902 'DEBUG_INFORMATION_FORMAT', 'dwarf-with-dsym', default='dwarf') and 903 self.spec['type'] != 'static_library'): 904 if not quiet: 905 result.append('echo DSYMUTIL\\(%s\\)' % self.spec['target_name']) 906 result.append('dsymutil %s -o %s' % (output_binary, output + '.dSYM')) 907 908 self.configname = None 909 return result 910 911 def _GetTargetPostbuilds(self, configname, output, output_binary, 912 quiet=False): 913 """Returns a list of shell commands that contain the shell commands 914 to run as postbuilds for this target, before the actual postbuilds.""" 915 # dSYMs need to build before stripping happens. 916 return ( 917 self._GetDebugInfoPostbuilds(configname, output, output_binary, quiet) + 918 self._GetStripPostbuilds(configname, output_binary, quiet)) 919 920 def _GetIOSPostbuilds(self, configname, output_binary): 921 """Return a shell command to codesign the iOS output binary so it can 922 be deployed to a device. This should be run as the very last step of the 923 build.""" 924 if not (self.isIOS and self.spec['type'] == "executable"): 925 return [] 926 927 settings = self.xcode_settings[configname] 928 key = self._GetIOSCodeSignIdentityKey(settings) 929 if not key: 930 return [] 931 932 # Warn for any unimplemented signing xcode keys. 933 unimpl = ['OTHER_CODE_SIGN_FLAGS'] 934 unimpl = set(unimpl) & set(self.xcode_settings[configname].keys()) 935 if unimpl: 936 print 'Warning: Some codesign keys not implemented, ignoring: %s' % ( 937 ', '.join(sorted(unimpl))) 938 939 return ['%s code-sign-bundle "%s" "%s" "%s" "%s"' % ( 940 os.path.join('${TARGET_BUILD_DIR}', 'gyp-mac-tool'), key, 941 settings.get('CODE_SIGN_RESOURCE_RULES_PATH', ''), 942 settings.get('CODE_SIGN_ENTITLEMENTS', ''), 943 settings.get('PROVISIONING_PROFILE', '')) 944 ] 945 946 def _GetIOSCodeSignIdentityKey(self, settings): 947 identity = settings.get('CODE_SIGN_IDENTITY') 948 if not identity: 949 return None 950 if identity not in XcodeSettings._codesigning_key_cache: 951 output = subprocess.check_output( 952 ['security', 'find-identity', '-p', 'codesigning', '-v']) 953 for line in output.splitlines(): 954 if identity in line: 955 fingerprint = line.split()[1] 956 cache = XcodeSettings._codesigning_key_cache 957 assert identity not in cache or fingerprint == cache[identity], ( 958 "Multiple codesigning fingerprints for identity: %s" % identity) 959 XcodeSettings._codesigning_key_cache[identity] = fingerprint 960 return XcodeSettings._codesigning_key_cache.get(identity, '') 961 962 def AddImplicitPostbuilds(self, configname, output, output_binary, 963 postbuilds=[], quiet=False): 964 """Returns a list of shell commands that should run before and after 965 |postbuilds|.""" 966 assert output_binary is not None 967 pre = self._GetTargetPostbuilds(configname, output, output_binary, quiet) 968 post = self._GetIOSPostbuilds(configname, output_binary) 969 return pre + postbuilds + post 970 971 def _AdjustLibrary(self, library, config_name=None): 972 if library.endswith('.framework'): 973 l = '-framework ' + os.path.splitext(os.path.basename(library))[0] 974 else: 975 m = self.library_re.match(library) 976 if m: 977 l = '-l' + m.group(1) 978 else: 979 l = library 980 981 sdk_root = self._SdkPath(config_name) 982 if not sdk_root: 983 sdk_root = '' 984 return l.replace('$(SDKROOT)', sdk_root) 985 986 def AdjustLibraries(self, libraries, config_name=None): 987 """Transforms entries like 'Cocoa.framework' in libraries into entries like 988 '-framework Cocoa', 'libcrypto.dylib' into '-lcrypto', etc. 989 """ 990 libraries = [self._AdjustLibrary(library, config_name) 991 for library in libraries] 992 return libraries 993 994 def _BuildMachineOSBuild(self): 995 return GetStdout(['sw_vers', '-buildVersion']) 996 997 def _XcodeIOSDeviceFamily(self, configname): 998 family = self.xcode_settings[configname].get('TARGETED_DEVICE_FAMILY', '1') 999 return [int(x) for x in family.split(',')] 1000 1001 def GetExtraPlistItems(self, configname=None): 1002 """Returns a dictionary with extra items to insert into Info.plist.""" 1003 if configname not in XcodeSettings._plist_cache: 1004 cache = {} 1005 cache['BuildMachineOSBuild'] = self._BuildMachineOSBuild() 1006 1007 xcode, xcode_build = XcodeVersion() 1008 cache['DTXcode'] = xcode 1009 cache['DTXcodeBuild'] = xcode_build 1010 1011 sdk_root = self._SdkRoot(configname) 1012 if not sdk_root: 1013 sdk_root = self._DefaultSdkRoot() 1014 cache['DTSDKName'] = sdk_root 1015 if xcode >= '0430': 1016 cache['DTSDKBuild'] = self._GetSdkVersionInfoItem( 1017 sdk_root, 'ProductBuildVersion') 1018 else: 1019 cache['DTSDKBuild'] = cache['BuildMachineOSBuild'] 1020 1021 if self.isIOS: 1022 cache['DTPlatformName'] = cache['DTSDKName'] 1023 if configname.endswith("iphoneos"): 1024 cache['DTPlatformVersion'] = self._GetSdkVersionInfoItem( 1025 sdk_root, 'ProductVersion') 1026 cache['CFBundleSupportedPlatforms'] = ['iPhoneOS'] 1027 else: 1028 cache['CFBundleSupportedPlatforms'] = ['iPhoneSimulator'] 1029 XcodeSettings._plist_cache[configname] = cache 1030 1031 # Include extra plist items that are per-target, not per global 1032 # XcodeSettings. 1033 items = dict(XcodeSettings._plist_cache[configname]) 1034 if self.isIOS: 1035 items['UIDeviceFamily'] = self._XcodeIOSDeviceFamily(configname) 1036 return items 1037 1038 def _DefaultSdkRoot(self): 1039 """Returns the default SDKROOT to use. 1040 1041 Prior to version 5.0.0, if SDKROOT was not explicitly set in the Xcode 1042 project, then the environment variable was empty. Starting with this 1043 version, Xcode uses the name of the newest SDK installed. 1044 """ 1045 xcode_version, xcode_build = XcodeVersion() 1046 if xcode_version < '0500': 1047 return '' 1048 default_sdk_path = self._XcodeSdkPath('') 1049 default_sdk_root = XcodeSettings._sdk_root_cache.get(default_sdk_path) 1050 if default_sdk_root: 1051 return default_sdk_root 1052 try: 1053 all_sdks = GetStdout(['xcodebuild', '-showsdks']) 1054 except: 1055 # If xcodebuild fails, there will be no valid SDKs 1056 return '' 1057 for line in all_sdks.splitlines(): 1058 items = line.split() 1059 if len(items) >= 3 and items[-2] == '-sdk': 1060 sdk_root = items[-1] 1061 sdk_path = self._XcodeSdkPath(sdk_root) 1062 if sdk_path == default_sdk_path: 1063 return sdk_root 1064 return '' 1065 1066 1067class MacPrefixHeader(object): 1068 """A class that helps with emulating Xcode's GCC_PREFIX_HEADER feature. 1069 1070 This feature consists of several pieces: 1071 * If GCC_PREFIX_HEADER is present, all compilations in that project get an 1072 additional |-include path_to_prefix_header| cflag. 1073 * If GCC_PRECOMPILE_PREFIX_HEADER is present too, then the prefix header is 1074 instead compiled, and all other compilations in the project get an 1075 additional |-include path_to_compiled_header| instead. 1076 + Compiled prefix headers have the extension gch. There is one gch file for 1077 every language used in the project (c, cc, m, mm), since gch files for 1078 different languages aren't compatible. 1079 + gch files themselves are built with the target's normal cflags, but they 1080 obviously don't get the |-include| flag. Instead, they need a -x flag that 1081 describes their language. 1082 + All o files in the target need to depend on the gch file, to make sure 1083 it's built before any o file is built. 1084 1085 This class helps with some of these tasks, but it needs help from the build 1086 system for writing dependencies to the gch files, for writing build commands 1087 for the gch files, and for figuring out the location of the gch files. 1088 """ 1089 def __init__(self, xcode_settings, 1090 gyp_path_to_build_path, gyp_path_to_build_output): 1091 """If xcode_settings is None, all methods on this class are no-ops. 1092 1093 Args: 1094 gyp_path_to_build_path: A function that takes a gyp-relative path, 1095 and returns a path relative to the build directory. 1096 gyp_path_to_build_output: A function that takes a gyp-relative path and 1097 a language code ('c', 'cc', 'm', or 'mm'), and that returns a path 1098 to where the output of precompiling that path for that language 1099 should be placed (without the trailing '.gch'). 1100 """ 1101 # This doesn't support per-configuration prefix headers. Good enough 1102 # for now. 1103 self.header = None 1104 self.compile_headers = False 1105 if xcode_settings: 1106 self.header = xcode_settings.GetPerTargetSetting('GCC_PREFIX_HEADER') 1107 self.compile_headers = xcode_settings.GetPerTargetSetting( 1108 'GCC_PRECOMPILE_PREFIX_HEADER', default='NO') != 'NO' 1109 self.compiled_headers = {} 1110 if self.header: 1111 if self.compile_headers: 1112 for lang in ['c', 'cc', 'm', 'mm']: 1113 self.compiled_headers[lang] = gyp_path_to_build_output( 1114 self.header, lang) 1115 self.header = gyp_path_to_build_path(self.header) 1116 1117 def _CompiledHeader(self, lang, arch): 1118 assert self.compile_headers 1119 h = self.compiled_headers[lang] 1120 if arch: 1121 h += '.' + arch 1122 return h 1123 1124 def GetInclude(self, lang, arch=None): 1125 """Gets the cflags to include the prefix header for language |lang|.""" 1126 if self.compile_headers and lang in self.compiled_headers: 1127 return '-include %s' % self._CompiledHeader(lang, arch) 1128 elif self.header: 1129 return '-include %s' % self.header 1130 else: 1131 return '' 1132 1133 def _Gch(self, lang, arch): 1134 """Returns the actual file name of the prefix header for language |lang|.""" 1135 assert self.compile_headers 1136 return self._CompiledHeader(lang, arch) + '.gch' 1137 1138 def GetObjDependencies(self, sources, objs, arch=None): 1139 """Given a list of source files and the corresponding object files, returns 1140 a list of (source, object, gch) tuples, where |gch| is the build-directory 1141 relative path to the gch file each object file depends on. |compilable[i]| 1142 has to be the source file belonging to |objs[i]|.""" 1143 if not self.header or not self.compile_headers: 1144 return [] 1145 1146 result = [] 1147 for source, obj in zip(sources, objs): 1148 ext = os.path.splitext(source)[1] 1149 lang = { 1150 '.c': 'c', 1151 '.cpp': 'cc', '.cc': 'cc', '.cxx': 'cc', 1152 '.m': 'm', 1153 '.mm': 'mm', 1154 }.get(ext, None) 1155 if lang: 1156 result.append((source, obj, self._Gch(lang, arch))) 1157 return result 1158 1159 def GetPchBuildCommands(self, arch=None): 1160 """Returns [(path_to_gch, language_flag, language, header)]. 1161 |path_to_gch| and |header| are relative to the build directory. 1162 """ 1163 if not self.header or not self.compile_headers: 1164 return [] 1165 return [ 1166 (self._Gch('c', arch), '-x c-header', 'c', self.header), 1167 (self._Gch('cc', arch), '-x c++-header', 'cc', self.header), 1168 (self._Gch('m', arch), '-x objective-c-header', 'm', self.header), 1169 (self._Gch('mm', arch), '-x objective-c++-header', 'mm', self.header), 1170 ] 1171 1172 1173def XcodeVersion(): 1174 """Returns a tuple of version and build version of installed Xcode.""" 1175 # `xcodebuild -version` output looks like 1176 # Xcode 4.6.3 1177 # Build version 4H1503 1178 # or like 1179 # Xcode 3.2.6 1180 # Component versions: DevToolsCore-1809.0; DevToolsSupport-1806.0 1181 # BuildVersion: 10M2518 1182 # Convert that to '0463', '4H1503'. 1183 global XCODE_VERSION_CACHE 1184 if XCODE_VERSION_CACHE: 1185 return XCODE_VERSION_CACHE 1186 try: 1187 version_list = GetStdout(['xcodebuild', '-version']).splitlines() 1188 # In some circumstances xcodebuild exits 0 but doesn't return 1189 # the right results; for example, a user on 10.7 or 10.8 with 1190 # a bogus path set via xcode-select 1191 # In that case this may be a CLT-only install so fall back to 1192 # checking that version. 1193 if len(version_list) < 2: 1194 raise GypError, "xcodebuild returned unexpected results" 1195 except: 1196 version = CLTVersion() 1197 if version: 1198 version = re.match('(\d\.\d\.?\d*)', version).groups()[0] 1199 else: 1200 raise GypError, "No Xcode or CLT version detected!" 1201 # The CLT has no build information, so we return an empty string. 1202 version_list = [version, ''] 1203 version = version_list[0] 1204 build = version_list[-1] 1205 # Be careful to convert "4.2" to "0420": 1206 version = version.split()[-1].replace('.', '') 1207 version = (version + '0' * (3 - len(version))).zfill(4) 1208 if build: 1209 build = build.split()[-1] 1210 XCODE_VERSION_CACHE = (version, build) 1211 return XCODE_VERSION_CACHE 1212 1213 1214# This function ported from the logic in Homebrew's CLT version check 1215def CLTVersion(): 1216 """Returns the version of command-line tools from pkgutil.""" 1217 # pkgutil output looks like 1218 # package-id: com.apple.pkg.CLTools_Executables 1219 # version: 5.0.1.0.1.1382131676 1220 # volume: / 1221 # location: / 1222 # install-time: 1382544035 1223 # groups: com.apple.FindSystemFiles.pkg-group com.apple.DevToolsBoth.pkg-group com.apple.DevToolsNonRelocatableShared.pkg-group 1224 STANDALONE_PKG_ID = "com.apple.pkg.DeveloperToolsCLILeo" 1225 FROM_XCODE_PKG_ID = "com.apple.pkg.DeveloperToolsCLI" 1226 MAVERICKS_PKG_ID = "com.apple.pkg.CLTools_Executables" 1227 1228 regex = re.compile('version: (?P<version>.+)') 1229 for key in [MAVERICKS_PKG_ID, STANDALONE_PKG_ID, FROM_XCODE_PKG_ID]: 1230 try: 1231 output = GetStdout(['/usr/sbin/pkgutil', '--pkg-info', key]) 1232 return re.search(regex, output).groupdict()['version'] 1233 except: 1234 continue 1235 1236 1237def GetStdout(cmdlist): 1238 """Returns the content of standard output returned by invoking |cmdlist|. 1239 Raises |GypError| if the command return with a non-zero return code.""" 1240 job = subprocess.Popen(cmdlist, stdout=subprocess.PIPE) 1241 out = job.communicate()[0] 1242 if job.returncode != 0: 1243 sys.stderr.write(out + '\n') 1244 raise GypError('Error %d running %s' % (job.returncode, cmdlist[0])) 1245 return out.rstrip('\n') 1246 1247 1248def MergeGlobalXcodeSettingsToSpec(global_dict, spec): 1249 """Merges the global xcode_settings dictionary into each configuration of the 1250 target represented by spec. For keys that are both in the global and the local 1251 xcode_settings dict, the local key gets precendence. 1252 """ 1253 # The xcode generator special-cases global xcode_settings and does something 1254 # that amounts to merging in the global xcode_settings into each local 1255 # xcode_settings dict. 1256 global_xcode_settings = global_dict.get('xcode_settings', {}) 1257 for config in spec['configurations'].values(): 1258 if 'xcode_settings' in config: 1259 new_settings = global_xcode_settings.copy() 1260 new_settings.update(config['xcode_settings']) 1261 config['xcode_settings'] = new_settings 1262 1263 1264def IsMacBundle(flavor, spec): 1265 """Returns if |spec| should be treated as a bundle. 1266 1267 Bundles are directories with a certain subdirectory structure, instead of 1268 just a single file. Bundle rules do not produce a binary but also package 1269 resources into that directory.""" 1270 is_mac_bundle = (int(spec.get('mac_bundle', 0)) != 0 and flavor == 'mac') 1271 if is_mac_bundle: 1272 assert spec['type'] != 'none', ( 1273 'mac_bundle targets cannot have type none (target "%s")' % 1274 spec['target_name']) 1275 return is_mac_bundle 1276 1277 1278def GetMacBundleResources(product_dir, xcode_settings, resources): 1279 """Yields (output, resource) pairs for every resource in |resources|. 1280 Only call this for mac bundle targets. 1281 1282 Args: 1283 product_dir: Path to the directory containing the output bundle, 1284 relative to the build directory. 1285 xcode_settings: The XcodeSettings of the current target. 1286 resources: A list of bundle resources, relative to the build directory. 1287 """ 1288 dest = os.path.join(product_dir, 1289 xcode_settings.GetBundleResourceFolder()) 1290 for res in resources: 1291 output = dest 1292 1293 # The make generator doesn't support it, so forbid it everywhere 1294 # to keep the generators more interchangable. 1295 assert ' ' not in res, ( 1296 "Spaces in resource filenames not supported (%s)" % res) 1297 1298 # Split into (path,file). 1299 res_parts = os.path.split(res) 1300 1301 # Now split the path into (prefix,maybe.lproj). 1302 lproj_parts = os.path.split(res_parts[0]) 1303 # If the resource lives in a .lproj bundle, add that to the destination. 1304 if lproj_parts[1].endswith('.lproj'): 1305 output = os.path.join(output, lproj_parts[1]) 1306 1307 output = os.path.join(output, res_parts[1]) 1308 # Compiled XIB files are referred to by .nib. 1309 if output.endswith('.xib'): 1310 output = os.path.splitext(output)[0] + '.nib' 1311 # Compiled storyboard files are referred to by .storyboardc. 1312 if output.endswith('.storyboard'): 1313 output = os.path.splitext(output)[0] + '.storyboardc' 1314 1315 yield output, res 1316 1317 1318def GetMacInfoPlist(product_dir, xcode_settings, gyp_path_to_build_path): 1319 """Returns (info_plist, dest_plist, defines, extra_env), where: 1320 * |info_plist| is the source plist path, relative to the 1321 build directory, 1322 * |dest_plist| is the destination plist path, relative to the 1323 build directory, 1324 * |defines| is a list of preprocessor defines (empty if the plist 1325 shouldn't be preprocessed, 1326 * |extra_env| is a dict of env variables that should be exported when 1327 invoking |mac_tool copy-info-plist|. 1328 1329 Only call this for mac bundle targets. 1330 1331 Args: 1332 product_dir: Path to the directory containing the output bundle, 1333 relative to the build directory. 1334 xcode_settings: The XcodeSettings of the current target. 1335 gyp_to_build_path: A function that converts paths relative to the 1336 current gyp file to paths relative to the build direcotry. 1337 """ 1338 info_plist = xcode_settings.GetPerTargetSetting('INFOPLIST_FILE') 1339 if not info_plist: 1340 return None, None, [], {} 1341 1342 # The make generator doesn't support it, so forbid it everywhere 1343 # to keep the generators more interchangable. 1344 assert ' ' not in info_plist, ( 1345 "Spaces in Info.plist filenames not supported (%s)" % info_plist) 1346 1347 info_plist = gyp_path_to_build_path(info_plist) 1348 1349 # If explicitly set to preprocess the plist, invoke the C preprocessor and 1350 # specify any defines as -D flags. 1351 if xcode_settings.GetPerTargetSetting( 1352 'INFOPLIST_PREPROCESS', default='NO') == 'YES': 1353 # Create an intermediate file based on the path. 1354 defines = shlex.split(xcode_settings.GetPerTargetSetting( 1355 'INFOPLIST_PREPROCESSOR_DEFINITIONS', default='')) 1356 else: 1357 defines = [] 1358 1359 dest_plist = os.path.join(product_dir, xcode_settings.GetBundlePlistPath()) 1360 extra_env = xcode_settings.GetPerTargetSettings() 1361 1362 return info_plist, dest_plist, defines, extra_env 1363 1364 1365def _GetXcodeEnv(xcode_settings, built_products_dir, srcroot, configuration, 1366 additional_settings=None): 1367 """Return the environment variables that Xcode would set. See 1368 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 1369 for a full list. 1370 1371 Args: 1372 xcode_settings: An XcodeSettings object. If this is None, this function 1373 returns an empty dict. 1374 built_products_dir: Absolute path to the built products dir. 1375 srcroot: Absolute path to the source root. 1376 configuration: The build configuration name. 1377 additional_settings: An optional dict with more values to add to the 1378 result. 1379 """ 1380 if not xcode_settings: return {} 1381 1382 # This function is considered a friend of XcodeSettings, so let it reach into 1383 # its implementation details. 1384 spec = xcode_settings.spec 1385 1386 # These are filled in on a as-needed basis. 1387 env = { 1388 'BUILT_PRODUCTS_DIR' : built_products_dir, 1389 'CONFIGURATION' : configuration, 1390 'PRODUCT_NAME' : xcode_settings.GetProductName(), 1391 # See /Developer/Platforms/MacOSX.platform/Developer/Library/Xcode/Specifications/MacOSX\ Product\ Types.xcspec for FULL_PRODUCT_NAME 1392 'SRCROOT' : srcroot, 1393 'SOURCE_ROOT': '${SRCROOT}', 1394 # This is not true for static libraries, but currently the env is only 1395 # written for bundles: 1396 'TARGET_BUILD_DIR' : built_products_dir, 1397 'TEMP_DIR' : '${TMPDIR}', 1398 } 1399 if xcode_settings.GetPerConfigSetting('SDKROOT', configuration): 1400 env['SDKROOT'] = xcode_settings._SdkPath(configuration) 1401 else: 1402 env['SDKROOT'] = '' 1403 1404 if spec['type'] in ( 1405 'executable', 'static_library', 'shared_library', 'loadable_module'): 1406 env['EXECUTABLE_NAME'] = xcode_settings.GetExecutableName() 1407 env['EXECUTABLE_PATH'] = xcode_settings.GetExecutablePath() 1408 env['FULL_PRODUCT_NAME'] = xcode_settings.GetFullProductName() 1409 mach_o_type = xcode_settings.GetMachOType() 1410 if mach_o_type: 1411 env['MACH_O_TYPE'] = mach_o_type 1412 env['PRODUCT_TYPE'] = xcode_settings.GetProductType() 1413 if xcode_settings._IsBundle(): 1414 env['CONTENTS_FOLDER_PATH'] = \ 1415 xcode_settings.GetBundleContentsFolderPath() 1416 env['UNLOCALIZED_RESOURCES_FOLDER_PATH'] = \ 1417 xcode_settings.GetBundleResourceFolder() 1418 env['INFOPLIST_PATH'] = xcode_settings.GetBundlePlistPath() 1419 env['WRAPPER_NAME'] = xcode_settings.GetWrapperName() 1420 1421 install_name = xcode_settings.GetInstallName() 1422 if install_name: 1423 env['LD_DYLIB_INSTALL_NAME'] = install_name 1424 install_name_base = xcode_settings.GetInstallNameBase() 1425 if install_name_base: 1426 env['DYLIB_INSTALL_NAME_BASE'] = install_name_base 1427 if XcodeVersion() >= '0500' and not env.get('SDKROOT'): 1428 sdk_root = xcode_settings._SdkRoot(configuration) 1429 if not sdk_root: 1430 sdk_root = xcode_settings._XcodeSdkPath('') 1431 env['SDKROOT'] = sdk_root 1432 1433 if not additional_settings: 1434 additional_settings = {} 1435 else: 1436 # Flatten lists to strings. 1437 for k in additional_settings: 1438 if not isinstance(additional_settings[k], str): 1439 additional_settings[k] = ' '.join(additional_settings[k]) 1440 additional_settings.update(env) 1441 1442 for k in additional_settings: 1443 additional_settings[k] = _NormalizeEnvVarReferences(additional_settings[k]) 1444 1445 return additional_settings 1446 1447 1448def _NormalizeEnvVarReferences(str): 1449 """Takes a string containing variable references in the form ${FOO}, $(FOO), 1450 or $FOO, and returns a string with all variable references in the form ${FOO}. 1451 """ 1452 # $FOO -> ${FOO} 1453 str = re.sub(r'\$([a-zA-Z_][a-zA-Z0-9_]*)', r'${\1}', str) 1454 1455 # $(FOO) -> ${FOO} 1456 matches = re.findall(r'(\$\(([a-zA-Z0-9\-_]+)\))', str) 1457 for match in matches: 1458 to_replace, variable = match 1459 assert '$(' not in match, '$($(FOO)) variables not supported: ' + match 1460 str = str.replace(to_replace, '${' + variable + '}') 1461 1462 return str 1463 1464 1465def ExpandEnvVars(string, expansions): 1466 """Expands ${VARIABLES}, $(VARIABLES), and $VARIABLES in string per the 1467 expansions list. If the variable expands to something that references 1468 another variable, this variable is expanded as well if it's in env -- 1469 until no variables present in env are left.""" 1470 for k, v in reversed(expansions): 1471 string = string.replace('${' + k + '}', v) 1472 string = string.replace('$(' + k + ')', v) 1473 string = string.replace('$' + k, v) 1474 return string 1475 1476 1477def _TopologicallySortedEnvVarKeys(env): 1478 """Takes a dict |env| whose values are strings that can refer to other keys, 1479 for example env['foo'] = '$(bar) and $(baz)'. Returns a list L of all keys of 1480 env such that key2 is after key1 in L if env[key2] refers to env[key1]. 1481 1482 Throws an Exception in case of dependency cycles. 1483 """ 1484 # Since environment variables can refer to other variables, the evaluation 1485 # order is important. Below is the logic to compute the dependency graph 1486 # and sort it. 1487 regex = re.compile(r'\$\{([a-zA-Z0-9\-_]+)\}') 1488 def GetEdges(node): 1489 # Use a definition of edges such that user_of_variable -> used_varible. 1490 # This happens to be easier in this case, since a variable's 1491 # definition contains all variables it references in a single string. 1492 # We can then reverse the result of the topological sort at the end. 1493 # Since: reverse(topsort(DAG)) = topsort(reverse_edges(DAG)) 1494 matches = set([v for v in regex.findall(env[node]) if v in env]) 1495 for dependee in matches: 1496 assert '${' not in dependee, 'Nested variables not supported: ' + dependee 1497 return matches 1498 1499 try: 1500 # Topologically sort, and then reverse, because we used an edge definition 1501 # that's inverted from the expected result of this function (see comment 1502 # above). 1503 order = gyp.common.TopologicallySorted(env.keys(), GetEdges) 1504 order.reverse() 1505 return order 1506 except gyp.common.CycleError, e: 1507 raise GypError( 1508 'Xcode environment variables are cyclically dependent: ' + str(e.nodes)) 1509 1510 1511def GetSortedXcodeEnv(xcode_settings, built_products_dir, srcroot, 1512 configuration, additional_settings=None): 1513 env = _GetXcodeEnv(xcode_settings, built_products_dir, srcroot, configuration, 1514 additional_settings) 1515 return [(key, env[key]) for key in _TopologicallySortedEnvVarKeys(env)] 1516 1517 1518def GetSpecPostbuildCommands(spec, quiet=False): 1519 """Returns the list of postbuilds explicitly defined on |spec|, in a form 1520 executable by a shell.""" 1521 postbuilds = [] 1522 for postbuild in spec.get('postbuilds', []): 1523 if not quiet: 1524 postbuilds.append('echo POSTBUILD\\(%s\\) %s' % ( 1525 spec['target_name'], postbuild['postbuild_name'])) 1526 postbuilds.append(gyp.common.EncodePOSIXShellList(postbuild['action'])) 1527 return postbuilds 1528 1529 1530def _HasIOSTarget(targets): 1531 """Returns true if any target contains the iOS specific key 1532 IPHONEOS_DEPLOYMENT_TARGET.""" 1533 for target_dict in targets.values(): 1534 for config in target_dict['configurations'].values(): 1535 if config.get('xcode_settings', {}).get('IPHONEOS_DEPLOYMENT_TARGET'): 1536 return True 1537 return False 1538 1539 1540def _AddIOSDeviceConfigurations(targets): 1541 """Clone all targets and append -iphoneos to the name. Configure these targets 1542 to build for iOS devices and use correct architectures for those builds.""" 1543 for target_dict in targets.itervalues(): 1544 toolset = target_dict['toolset'] 1545 configs = target_dict['configurations'] 1546 for config_name, config_dict in dict(configs).iteritems(): 1547 iphoneos_config_dict = copy.deepcopy(config_dict) 1548 configs[config_name + '-iphoneos'] = iphoneos_config_dict 1549 configs[config_name + '-iphonesimulator'] = config_dict 1550 if toolset == 'target': 1551 iphoneos_config_dict['xcode_settings']['SDKROOT'] = 'iphoneos' 1552 return targets 1553 1554def CloneConfigurationForDeviceAndEmulator(target_dicts): 1555 """If |target_dicts| contains any iOS targets, automatically create -iphoneos 1556 targets for iOS device builds.""" 1557 if _HasIOSTarget(target_dicts): 1558 return _AddIOSDeviceConfigurations(target_dicts) 1559 return target_dicts 1560