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