summaryrefslogtreecommitdiff
path: root/tools/gyp/pylib/gyp/xcode_emulation.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/gyp/pylib/gyp/xcode_emulation.py')
-rw-r--r--tools/gyp/pylib/gyp/xcode_emulation.py313
1 files changed, 271 insertions, 42 deletions
diff --git a/tools/gyp/pylib/gyp/xcode_emulation.py b/tools/gyp/pylib/gyp/xcode_emulation.py
index 806f92b57..f9cec3363 100644
--- a/tools/gyp/pylib/gyp/xcode_emulation.py
+++ b/tools/gyp/pylib/gyp/xcode_emulation.py
@@ -7,6 +7,7 @@ This module contains classes that help to emulate xcodebuild behavior on top of
other build systems, such as make and ninja.
"""
+import copy
import gyp.common
import os.path
import re
@@ -22,9 +23,19 @@ class XcodeSettings(object):
# at class-level for efficiency.
_sdk_path_cache = {}
+ # Populated lazily by GetExtraPlistItems(). Shared by all XcodeSettings, so
+ # cached at class-level for efficiency.
+ _plist_cache = {}
+
+ # Populated lazily by GetIOSPostbuilds. Shared by all XcodeSettings, so
+ # cached at class-level for efficiency.
+ _codesigning_key_cache = {}
+
def __init__(self, spec):
self.spec = spec
+ self.isIOS = False
+
# Per-target 'xcode_settings' are pushed down into configs earlier by gyp.
# This means self.xcode_settings[config] always contains all settings
# for that config -- the per-target settings as well. Settings that are
@@ -33,6 +44,10 @@ class XcodeSettings(object):
configs = spec['configurations']
for configname, config in configs.iteritems():
self.xcode_settings[configname] = config.get('xcode_settings', {})
+ self._ConvertConditionalKeys(configname)
+ if self.xcode_settings[configname].get('IPHONEOS_DEPLOYMENT_TARGET',
+ None):
+ self.isIOS = True
# This is only non-None temporarily during the execution of some methods.
self.configname = None
@@ -40,6 +55,23 @@ class XcodeSettings(object):
# Used by _AdjustLibrary to match .a and .dylib entries in libraries.
self.library_re = re.compile(r'^lib([^/]+)\.(a|dylib)$')
+ def _ConvertConditionalKeys(self, configname):
+ """Converts or warns on conditional keys. Xcode supports conditional keys,
+ such as CODE_SIGN_IDENTITY[sdk=iphoneos*]. This is a partial implementation
+ with some keys converted while the rest force a warning."""
+ settings = self.xcode_settings[configname]
+ conditional_keys = [key for key in settings if key.endswith(']')]
+ for key in conditional_keys:
+ # If you need more, speak up at http://crbug.com/122592
+ if key.endswith("[sdk=iphoneos*]"):
+ if configname.endswith("iphoneos"):
+ new_key = key.split("[")[0]
+ settings[new_key] = settings[key]
+ else:
+ print 'Warning: Conditional keys not implemented, ignoring:', \
+ ' '.join(conditional_keys)
+ del settings[key]
+
def _Settings(self):
assert self.configname
return self.xcode_settings[self.configname]
@@ -79,7 +111,7 @@ class XcodeSettings(object):
'WRAPPER_EXTENSION', default=default_wrapper_extension)
return '.' + self.spec.get('product_extension', wrapper_extension)
elif self.spec['type'] == 'executable':
- return '.app'
+ return '.' + self.spec.get('product_extension', 'app')
else:
assert False, "Don't know extension for '%s', target '%s'" % (
self.spec['type'], self.spec['target_name'])
@@ -104,6 +136,8 @@ class XcodeSettings(object):
def GetBundleContentsFolderPath(self):
"""Returns the qualified path to the bundle's contents folder. E.g.
Chromium.app/Contents or Foo.bundle/Versions/A. Only valid for bundles."""
+ if self.isIOS:
+ return self.GetWrapperName()
assert self._IsBundle()
if self.spec['type'] == 'shared_library':
return os.path.join(
@@ -116,6 +150,8 @@ class XcodeSettings(object):
"""Returns the qualified path to the bundle's resource folder. E.g.
Chromium.app/Contents/Resources. Only valid for bundles."""
assert self._IsBundle()
+ if self.isIOS:
+ return self.GetBundleContentsFolderPath()
return os.path.join(self.GetBundleContentsFolderPath(), 'Resources')
def GetBundlePlistPath(self):
@@ -160,7 +196,7 @@ class XcodeSettings(object):
"""Returns the name of the bundle binary of by this target.
E.g. Chromium.app/Contents/MacOS/Chromium. Only valid for bundles."""
assert self._IsBundle()
- if self.spec['type'] in ('shared_library'):
+ if self.spec['type'] in ('shared_library') or self.isIOS:
path = self.GetBundleContentsFolderPath()
elif self.spec['type'] in ('executable', 'loadable_module'):
path = os.path.join(self.GetBundleContentsFolderPath(), 'MacOS')
@@ -222,18 +258,32 @@ class XcodeSettings(object):
else:
return self._GetStandaloneBinaryPath()
- def _GetSdkVersionInfoItem(self, sdk, infoitem):
- job = subprocess.Popen(['xcodebuild', '-version', '-sdk', sdk, infoitem],
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT)
+ def GetActiveArchs(self, configname):
+ """Returns the architectures this target should be built for."""
+ # TODO: Look at VALID_ARCHS, ONLY_ACTIVE_ARCH; possibly set
+ # CURRENT_ARCH / NATIVE_ARCH env vars?
+ return self.xcode_settings[configname].get('ARCHS', ['i386'])
+
+ def _GetStdout(self, cmdlist):
+ job = subprocess.Popen(cmdlist, stdout=subprocess.PIPE)
out = job.communicate()[0]
if job.returncode != 0:
sys.stderr.write(out + '\n')
- raise GypError('Error %d running xcodebuild' % job.returncode)
+ raise GypError('Error %d running %s' % (job.returncode, cmdlist[0]))
return out.rstrip('\n')
- def _SdkPath(self):
- sdk_root = self.GetPerTargetSetting('SDKROOT', default='macosx')
+ def _GetSdkVersionInfoItem(self, sdk, infoitem):
+ return self._GetStdout(['xcodebuild', '-version', '-sdk', sdk, infoitem])
+
+ def _SdkRoot(self, configname):
+ if configname is None:
+ configname = self.configname
+ return self.GetPerConfigSetting('SDKROOT', configname, default='')
+
+ def _SdkPath(self, configname=None):
+ sdk_root = self._SdkRoot(configname)
+ if sdk_root.startswith('/'):
+ return sdk_root
if sdk_root not in XcodeSettings._sdk_path_cache:
XcodeSettings._sdk_path_cache[sdk_root] = self._GetSdkVersionInfoItem(
sdk_root, 'Path')
@@ -251,7 +301,7 @@ class XcodeSettings(object):
self._Appendf(lst, 'IPHONEOS_DEPLOYMENT_TARGET',
'-miphoneos-version-min=%s')
- def GetCflags(self, configname):
+ def GetCflags(self, configname, arch=None):
"""Returns flags that need to be added to .c, .cc, .m, and .mm
compilations."""
# This functions (and the similar ones below) do not offer complete
@@ -298,6 +348,11 @@ class XcodeSettings(object):
else:
raise NotImplementedError('Unknown debug format %s' % dbg_format)
+ if self._Settings().get('GCC_STRICT_ALIASING') == 'YES':
+ cflags.append('-fstrict-aliasing')
+ elif self._Settings().get('GCC_STRICT_ALIASING') == 'NO':
+ cflags.append('-fno-strict-aliasing')
+
if self._Test('GCC_SYMBOLS_PRIVATE_EXTERN', 'YES', default='NO'):
cflags.append('-fvisibility=hidden')
@@ -319,7 +374,10 @@ class XcodeSettings(object):
self._WarnUnimplemented('MACH_O_TYPE')
self._WarnUnimplemented('PRODUCT_TYPE')
- archs = self._Settings().get('ARCHS', ['i386'])
+ if arch is not None:
+ archs = [arch]
+ else:
+ archs = self._Settings().get('ARCHS', ['i386'])
if len(archs) != 1:
# TODO: Supporting fat binaries will be annoying.
self._WarnUnimplemented('ARCHS')
@@ -351,7 +409,10 @@ class XcodeSettings(object):
"""Returns flags that need to be added to .c, and .m compilations."""
self.configname = configname
cflags_c = []
- self._Appendf(cflags_c, 'GCC_C_LANGUAGE_STANDARD', '-std=%s')
+ if self._Settings().get('GCC_C_LANGUAGE_STANDARD', '') == 'ansi':
+ cflags_c.append('-ansi')
+ else:
+ self._Appendf(cflags_c, 'GCC_C_LANGUAGE_STANDARD', '-std=%s')
cflags_c += self._Settings().get('OTHER_CFLAGS', [])
self.configname = None
return cflags_c
@@ -404,13 +465,22 @@ class XcodeSettings(object):
elif gc_policy == 'required':
flags.append('-fobjc-gc-only')
+ def _AddObjectiveCARCFlags(self, flags):
+ if self._Test('CLANG_ENABLE_OBJC_ARC', 'YES', default='NO'):
+ flags.append('-fobjc-arc')
+
+ def _AddObjectiveCMissingPropertySynthesisFlags(self, flags):
+ if self._Test('CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS',
+ 'YES', default='NO'):
+ flags.append('-Wobjc-missing-property-synthesis')
+
def GetCflagsObjC(self, configname):
"""Returns flags that need to be added to .m compilations."""
self.configname = configname
cflags_objc = []
-
self._AddObjectiveCGarbageCollectionFlags(cflags_objc)
-
+ self._AddObjectiveCARCFlags(cflags_objc)
+ self._AddObjectiveCMissingPropertySynthesisFlags(cflags_objc)
self.configname = None
return cflags_objc
@@ -419,6 +489,8 @@ class XcodeSettings(object):
self.configname = configname
cflags_objcc = []
self._AddObjectiveCGarbageCollectionFlags(cflags_objcc)
+ self._AddObjectiveCARCFlags(cflags_objcc)
+ self._AddObjectiveCMissingPropertySynthesisFlags(cflags_objcc)
if self._Test('GCC_OBJC_CALL_CXX_CDTORS', 'YES', default='NO'):
cflags_objcc.append('-fobjc-call-cxx-cdtors')
self.configname = None
@@ -513,7 +585,7 @@ class XcodeSettings(object):
ldflag = '-L' + gyp_to_build_path(ldflag[len('-L'):])
return ldflag
- def GetLdflags(self, configname, product_dir, gyp_to_build_path):
+ def GetLdflags(self, configname, product_dir, gyp_to_build_path, arch=None):
"""Returns flags that need to be passed to the linker.
Args:
@@ -555,7 +627,10 @@ class XcodeSettings(object):
'-Wl,' + gyp_to_build_path(
self._Settings()['ORDER_FILE']))
- archs = self._Settings().get('ARCHS', ['i386'])
+ if arch is not None:
+ archs = [arch]
+ else:
+ archs = self._Settings().get('ARCHS', ['i386'])
if len(archs) != 1:
# TODO: Supporting fat binaries will be annoying.
self._WarnUnimplemented('ARCHS')
@@ -566,7 +641,7 @@ class XcodeSettings(object):
ldflags.append('-L' + product_dir)
install_name = self.GetInstallName()
- if install_name:
+ if install_name and self.spec['type'] != 'loadable_module':
ldflags.append('-install_name ' + install_name.replace(' ', r'\ '))
for rpath in self._Settings().get('LD_RUNPATH_SEARCH_PATHS', []):
@@ -613,19 +688,25 @@ class XcodeSettings(object):
del result[key]
return result
+ def GetPerConfigSetting(self, setting, configname, default=None):
+ if configname in self.xcode_settings:
+ return self.xcode_settings[configname].get(setting, default)
+ else:
+ return self.GetPerTargetSetting(setting, default)
+
def GetPerTargetSetting(self, setting, default=None):
"""Tries to get xcode_settings.setting from spec. Assumes that the setting
has the same value in all configurations and throws otherwise."""
- first_pass = True
+ is_first_pass = True
result = None
for configname in sorted(self.xcode_settings.keys()):
- if first_pass:
+ if is_first_pass:
result = self.xcode_settings[configname].get(setting, None)
- first_pass = False
+ is_first_pass = False
else:
assert result == self.xcode_settings[configname].get(setting, None), (
"Expected per-target setting for '%s', got per-config setting "
- "(target %s)" % (setting, spec['target_name']))
+ "(target %s)" % (setting, self.spec['target_name']))
if result is None:
return default
return result
@@ -641,7 +722,7 @@ class XcodeSettings(object):
self._Test('STRIP_INSTALLED_PRODUCT', 'YES', default='NO')):
default_strip_style = 'debugging'
- if self._IsBundle():
+ if self.spec['type'] == 'loadable_module' and self._IsBundle():
default_strip_style = 'non-global'
elif self.spec['type'] == 'executable':
default_strip_style = 'all'
@@ -683,7 +764,8 @@ class XcodeSettings(object):
self.configname = None
return result
- def GetTargetPostbuilds(self, configname, output, output_binary, quiet=False):
+ def _GetTargetPostbuilds(self, configname, output, output_binary,
+ quiet=False):
"""Returns a list of shell commands that contain the shell commands
to run as postbuilds for this target, before the actual postbuilds."""
# dSYMs need to build before stripping happens.
@@ -691,7 +773,51 @@ class XcodeSettings(object):
self._GetDebugInfoPostbuilds(configname, output, output_binary, quiet) +
self._GetStripPostbuilds(configname, output_binary, quiet))
- def _AdjustLibrary(self, library):
+ def _GetIOSPostbuilds(self, configname, output_binary):
+ """Return a shell command to codesign the iOS output binary so it can
+ be deployed to a device. This should be run as the very last step of the
+ build."""
+ if not (self.isIOS and self.spec['type'] == "executable"):
+ return []
+
+ identity = self.xcode_settings[configname].get('CODE_SIGN_IDENTITY', '')
+ if identity == '':
+ return []
+ if identity not in XcodeSettings._codesigning_key_cache:
+ proc = subprocess.Popen(['security', 'find-identity', '-p', 'codesigning',
+ '-v'], stdout=subprocess.PIPE)
+ output = proc.communicate()[0].strip()
+ key = None
+ for item in output.split("\n"):
+ if identity in item:
+ assert key == None, (
+ "Multiple codesigning identities for identity: %s" %
+ identity)
+ key = item.split(' ')[1]
+ XcodeSettings._codesigning_key_cache[identity] = key
+ key = XcodeSettings._codesigning_key_cache[identity]
+ if key:
+ # Warn for any unimplemented signing xcode keys.
+ unimpl = ['CODE_SIGN_RESOURCE_RULES_PATH', 'OTHER_CODE_SIGN_FLAGS',
+ 'CODE_SIGN_ENTITLEMENTS']
+ keys = set(self.xcode_settings[configname].keys())
+ unimpl = set(unimpl) & keys
+ if unimpl:
+ print 'Warning: Some codesign keys not implemented, ignoring:', \
+ ' '.join(unimpl)
+ return ['codesign --force --sign %s %s' % (key, output_binary)]
+ return []
+
+ def AddImplicitPostbuilds(self, configname, output, output_binary,
+ postbuilds=[], quiet=False):
+ """Returns a list of shell commands that should run before and after
+ |postbuilds|."""
+ assert output_binary is not None
+ pre = self._GetTargetPostbuilds(configname, output, output_binary, quiet)
+ post = self._GetIOSPostbuilds(configname, output_binary)
+ return pre + postbuilds + post
+
+ def _AdjustLibrary(self, library, config_name=None):
if library.endswith('.framework'):
l = '-framework ' + os.path.splitext(os.path.basename(library))[0]
else:
@@ -700,15 +826,76 @@ class XcodeSettings(object):
l = '-l' + m.group(1)
else:
l = library
- return l.replace('$(SDKROOT)', self._SdkPath())
+ return l.replace('$(SDKROOT)', self._SdkPath(config_name))
- def AdjustLibraries(self, libraries):
+ def AdjustLibraries(self, libraries, config_name=None):
"""Transforms entries like 'Cocoa.framework' in libraries into entries like
'-framework Cocoa', 'libcrypto.dylib' into '-lcrypto', etc.
"""
- libraries = [ self._AdjustLibrary(library) for library in libraries]
+ libraries = [self._AdjustLibrary(library, config_name)
+ for library in libraries]
return libraries
+ def _BuildMachineOSBuild(self):
+ return self._GetStdout(['sw_vers', '-buildVersion'])
+
+ def _XcodeVersion(self):
+ # `xcodebuild -version` output looks like
+ # Xcode 4.6.3
+ # Build version 4H1503
+ # or like
+ # Xcode 3.2.6
+ # Component versions: DevToolsCore-1809.0; DevToolsSupport-1806.0
+ # BuildVersion: 10M2518
+ # Convert that to '0463', '4H1503'.
+ version_list = self._GetStdout(['xcodebuild', '-version']).splitlines()
+ version = version_list[0]
+ build = version_list[-1]
+ # Be careful to convert "4.2" to "0420":
+ version = version.split()[-1].replace('.', '')
+ version = (version + '0' * (3 - len(version))).zfill(4)
+ build = build.split()[-1]
+ return version, build
+
+ def _XcodeIOSDeviceFamily(self, configname):
+ family = self.xcode_settings[configname].get('TARGETED_DEVICE_FAMILY', '1')
+ return [int(x) for x in family.split(',')]
+
+ def GetExtraPlistItems(self, configname=None):
+ """Returns a dictionary with extra items to insert into Info.plist."""
+ if configname not in XcodeSettings._plist_cache:
+ cache = {}
+ cache['BuildMachineOSBuild'] = self._BuildMachineOSBuild()
+
+ xcode, xcode_build = self._XcodeVersion()
+ cache['DTXcode'] = xcode
+ cache['DTXcodeBuild'] = xcode_build
+
+ sdk_root = self._SdkRoot(configname)
+ cache['DTSDKName'] = sdk_root
+ if xcode >= '0430':
+ cache['DTSDKBuild'] = self._GetSdkVersionInfoItem(
+ sdk_root, 'ProductBuildVersion')
+ else:
+ cache['DTSDKBuild'] = cache['BuildMachineOSBuild']
+
+ if self.isIOS:
+ cache['DTPlatformName'] = cache['DTSDKName']
+ if configname.endswith("iphoneos"):
+ cache['DTPlatformVersion'] = self._GetSdkVersionInfoItem(
+ sdk_root, 'ProductVersion')
+ cache['CFBundleSupportedPlatforms'] = ['iPhoneOS']
+ else:
+ cache['CFBundleSupportedPlatforms'] = ['iPhoneSimulator']
+ XcodeSettings._plist_cache[configname] = cache
+
+ # Include extra plist items that are per-target, not per global
+ # XcodeSettings.
+ items = dict(XcodeSettings._plist_cache[configname])
+ if self.isIOS:
+ items['UIDeviceFamily'] = self._XcodeIOSDeviceFamily(configname)
+ return items
+
class MacPrefixHeader(object):
"""A class that helps with emulating Xcode's GCC_PREFIX_HEADER feature.
@@ -760,21 +947,28 @@ class MacPrefixHeader(object):
self.header, lang)
self.header = gyp_path_to_build_path(self.header)
- def GetInclude(self, lang):
+ def _CompiledHeader(self, lang, arch):
+ assert self.compile_headers
+ h = self.compiled_headers[lang]
+ if arch:
+ h += '.' + arch
+ return h
+
+ def GetInclude(self, lang, arch=None):
"""Gets the cflags to include the prefix header for language |lang|."""
if self.compile_headers and lang in self.compiled_headers:
- return '-include %s' % self.compiled_headers[lang]
+ return '-include %s' % self._CompiledHeader(lang, arch)
elif self.header:
return '-include %s' % self.header
else:
return ''
- def _Gch(self, lang):
+ def _Gch(self, lang, arch):
"""Returns the actual file name of the prefix header for language |lang|."""
assert self.compile_headers
- return self.compiled_headers[lang] + '.gch'
+ return self._CompiledHeader(lang, arch) + '.gch'
- def GetObjDependencies(self, sources, objs):
+ def GetObjDependencies(self, sources, objs, arch=None):
"""Given a list of source files and the corresponding object files, returns
a list of (source, object, gch) tuples, where |gch| is the build-directory
relative path to the gch file each object file depends on. |compilable[i]|
@@ -792,20 +986,20 @@ class MacPrefixHeader(object):
'.mm': 'mm',
}.get(ext, None)
if lang:
- result.append((source, obj, self._Gch(lang)))
+ result.append((source, obj, self._Gch(lang, arch)))
return result
- def GetPchBuildCommands(self):
+ def GetPchBuildCommands(self, arch=None):
"""Returns [(path_to_gch, language_flag, language, header)].
|path_to_gch| and |header| are relative to the build directory.
"""
if not self.header or not self.compile_headers:
return []
return [
- (self._Gch('c'), '-x c-header', 'c', self.header),
- (self._Gch('cc'), '-x c++-header', 'cc', self.header),
- (self._Gch('m'), '-x objective-c-header', 'm', self.header),
- (self._Gch('mm'), '-x objective-c++-header', 'mm', self.header),
+ (self._Gch('c', arch), '-x c-header', 'c', self.header),
+ (self._Gch('cc', arch), '-x c++-header', 'cc', self.header),
+ (self._Gch('m', arch), '-x objective-c-header', 'm', self.header),
+ (self._Gch('mm', arch), '-x objective-c++-header', 'mm', self.header),
]
@@ -871,14 +1065,17 @@ def GetMacBundleResources(product_dir, xcode_settings, resources):
output = os.path.join(output, res_parts[1])
# Compiled XIB files are referred to by .nib.
if output.endswith('.xib'):
- output = output[0:-3] + 'nib'
+ output = os.path.splitext(output)[0] + '.nib'
+ # Compiled storyboard files are referred to by .storyboardc.
+ if output.endswith('.storyboard'):
+ output = os.path.splitext(output)[0] + '.storyboardc'
yield output, res
def GetMacInfoPlist(product_dir, xcode_settings, gyp_path_to_build_path):
"""Returns (info_plist, dest_plist, defines, extra_env), where:
- * |info_plist| is the sourc plist path, relative to the
+ * |info_plist| is the source plist path, relative to the
build directory,
* |dest_plist| is the destination plist path, relative to the
build directory,
@@ -957,8 +1154,8 @@ def _GetXcodeEnv(xcode_settings, built_products_dir, srcroot, configuration,
'TARGET_BUILD_DIR' : built_products_dir,
'TEMP_DIR' : '${TMPDIR}',
}
- if xcode_settings.GetPerTargetSetting('SDKROOT'):
- env['SDKROOT'] = xcode_settings._SdkPath()
+ if xcode_settings.GetPerConfigSetting('SDKROOT', configuration):
+ env['SDKROOT'] = xcode_settings._SdkPath(configuration)
else:
env['SDKROOT'] = ''
@@ -1081,3 +1278,35 @@ def GetSpecPostbuildCommands(spec, quiet=False):
spec['target_name'], postbuild['postbuild_name']))
postbuilds.append(gyp.common.EncodePOSIXShellList(postbuild['action']))
return postbuilds
+
+
+def _HasIOSTarget(targets):
+ """Returns true if any target contains the iOS specific key
+ IPHONEOS_DEPLOYMENT_TARGET."""
+ for target_dict in targets.values():
+ for config in target_dict['configurations'].values():
+ if config.get('xcode_settings', {}).get('IPHONEOS_DEPLOYMENT_TARGET'):
+ return True
+ return False
+
+
+def _AddIOSDeviceConfigurations(targets):
+ """Clone all targets and append -iphoneos to the name. Configure these targets
+ to build for iOS devices."""
+ for target_dict in targets.values():
+ for config_name in target_dict['configurations'].keys():
+ config = target_dict['configurations'][config_name]
+ new_config_name = config_name + '-iphoneos'
+ new_config_dict = copy.deepcopy(config)
+ if target_dict['toolset'] == 'target':
+ new_config_dict['xcode_settings']['ARCHS'] = ['armv7']
+ new_config_dict['xcode_settings']['SDKROOT'] = 'iphoneos'
+ target_dict['configurations'][new_config_name] = new_config_dict
+ return targets
+
+def CloneConfigurationForDeviceAndEmulator(target_dicts):
+ """If |target_dicts| contains any iOS targets, automatically create -iphoneos
+ targets for iOS device builds."""
+ if _HasIOSTarget(target_dicts):
+ return _AddIOSDeviceConfigurations(target_dicts)
+ return target_dicts