Ben Murdoch | 097c5b2 | 2016-05-18 11:27:45 +0100 | [diff] [blame] | 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 | """ |
| 6 | This module contains classes that help to emulate xcodebuild behavior on top of |
| 7 | other build systems, such as make and ninja. |
| 8 | """ |
| 9 | |
| 10 | import copy |
| 11 | import gyp.common |
| 12 | import os |
| 13 | import os.path |
| 14 | import re |
| 15 | import shlex |
| 16 | import subprocess |
| 17 | import sys |
| 18 | import tempfile |
| 19 | from 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). |
| 24 | XCODE_VERSION_CACHE = None |
| 25 | |
| 26 | # Populated lazily by GetXcodeArchsDefault, to an |XcodeArchsDefault| instance |
| 27 | # corresponding to the installed version of Xcode. |
| 28 | XCODE_ARCHS_DEFAULT_CACHE = None |
| 29 | |
| 30 | |
| 31 | def 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 | |
| 39 | class 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 | |
| 95 | def 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 | |
| 144 | class 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 | |
| 1228 | class 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 | |
| 1334 | def 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 |
| 1376 | def 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 | |
| 1398 | def 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 | |
| 1409 | def 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 | |
| 1425 | def 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 | |
| 1442 | def 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 | |
| 1482 | def 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 | |
| 1529 | def _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 | |
| 1617 | def _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 | |
| 1634 | def 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 | |
| 1646 | def _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 | |
| 1680 | def 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 | |
| 1687 | def 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 | |
| 1699 | def _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 | |
| 1709 | def _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 | |
| 1723 | def 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 |