Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / tools / gyp / pylib / gyp / xcode_emulation.py
index f2c1fd9..d856e57 100644 (file)
@@ -18,12 +18,136 @@ import sys
 import tempfile
 from gyp.common import GypError
 
+# Populated lazily by XcodeVersion, for efficiency, and to fix an issue when
+# "xcodebuild" is called too quickly (it has been found to return incorrect
+# version number).
+XCODE_VERSION_CACHE = None
+
+# Populated lazily by GetXcodeArchsDefault, to an |XcodeArchsDefault| instance
+# corresponding to the installed version of Xcode.
+XCODE_ARCHS_DEFAULT_CACHE = None
+
+
+def XcodeArchsVariableMapping(archs, archs_including_64_bit=None):
+  """Constructs a dictionary with expansion for $(ARCHS_STANDARD) variable,
+  and optionally for $(ARCHS_STANDARD_INCLUDING_64_BIT)."""
+  mapping = {'$(ARCHS_STANDARD)': archs}
+  if archs_including_64_bit:
+    mapping['$(ARCHS_STANDARD_INCLUDING_64_BIT)'] = archs_including_64_bit
+  return mapping
+
+class XcodeArchsDefault(object):
+  """A class to resolve ARCHS variable from xcode_settings, resolving Xcode
+  macros and implementing filtering by VALID_ARCHS. The expansion of macros
+  depends on the SDKROOT used ("macosx", "iphoneos", "iphonesimulator") and
+  on the version of Xcode.
+  """
+
+  # Match variable like $(ARCHS_STANDARD).
+  variable_pattern = re.compile(r'\$\([a-zA-Z_][a-zA-Z0-9_]*\)$')
+
+  def __init__(self, default, mac, iphonesimulator, iphoneos):
+    self._default = (default,)
+    self._archs = {'mac': mac, 'ios': iphoneos, 'iossim': iphonesimulator}
+
+  def _VariableMapping(self, sdkroot):
+    """Returns the dictionary of variable mapping depending on the SDKROOT."""
+    sdkroot = sdkroot.lower()
+    if 'iphoneos' in sdkroot:
+      return self._archs['ios']
+    elif 'iphonesimulator' in sdkroot:
+      return self._archs['iossim']
+    else:
+      return self._archs['mac']
+
+  def _ExpandArchs(self, archs, sdkroot):
+    """Expands variables references in ARCHS, and remove duplicates."""
+    variable_mapping = self._VariableMapping(sdkroot)
+    expanded_archs = []
+    for arch in archs:
+      if self.variable_pattern.match(arch):
+        variable = arch
+        try:
+          variable_expansion = variable_mapping[variable]
+          for arch in variable_expansion:
+            if arch not in expanded_archs:
+              expanded_archs.append(arch)
+        except KeyError as e:
+          print 'Warning: Ignoring unsupported variable "%s".' % variable
+      elif arch not in expanded_archs:
+        expanded_archs.append(arch)
+    return expanded_archs
+
+  def ActiveArchs(self, archs, valid_archs, sdkroot):
+    """Expands variables references in ARCHS, and filter by VALID_ARCHS if it
+    is defined (if not set, Xcode accept any value in ARCHS, otherwise, only
+    values present in VALID_ARCHS are kept)."""
+    expanded_archs = self._ExpandArchs(archs or self._default, sdkroot or '')
+    if valid_archs:
+      filtered_archs = []
+      for arch in expanded_archs:
+        if arch in valid_archs:
+          filtered_archs.append(arch)
+      expanded_archs = filtered_archs
+    return expanded_archs
+
+
+def GetXcodeArchsDefault():
+  """Returns the |XcodeArchsDefault| object to use to expand ARCHS for the
+  installed version of Xcode. The default values used by Xcode for ARCHS
+  and the expansion of the variables depends on the version of Xcode used.
+
+  For all version anterior to Xcode 5.0 or posterior to Xcode 5.1 included
+  uses $(ARCHS_STANDARD) if ARCHS is unset, while Xcode 5.0 to 5.0.2 uses
+  $(ARCHS_STANDARD_INCLUDING_64_BIT). This variable was added to Xcode 5.0
+  and deprecated with Xcode 5.1.
+
+  For "macosx" SDKROOT, all version starting with Xcode 5.0 includes 64-bit
+  architecture as part of $(ARCHS_STANDARD) and default to only building it.
+
+  For "iphoneos" and "iphonesimulator" SDKROOT, 64-bit architectures are part
+  of $(ARCHS_STANDARD_INCLUDING_64_BIT) from Xcode 5.0. From Xcode 5.1, they
+  are also part of $(ARCHS_STANDARD).
+
+  All thoses rules are coded in the construction of the |XcodeArchsDefault|
+  object to use depending on the version of Xcode detected. The object is
+  for performance reason."""
+  global XCODE_ARCHS_DEFAULT_CACHE
+  if XCODE_ARCHS_DEFAULT_CACHE:
+    return XCODE_ARCHS_DEFAULT_CACHE
+  xcode_version, _ = XcodeVersion()
+  if xcode_version < '0500':
+    XCODE_ARCHS_DEFAULT_CACHE = XcodeArchsDefault(
+        '$(ARCHS_STANDARD)',
+        XcodeArchsVariableMapping(['i386']),
+        XcodeArchsVariableMapping(['i386']),
+        XcodeArchsVariableMapping(['armv7']))
+  elif xcode_version < '0510':
+    XCODE_ARCHS_DEFAULT_CACHE = XcodeArchsDefault(
+        '$(ARCHS_STANDARD_INCLUDING_64_BIT)',
+        XcodeArchsVariableMapping(['x86_64'], ['x86_64']),
+        XcodeArchsVariableMapping(['i386'], ['i386', 'x86_64']),
+        XcodeArchsVariableMapping(
+            ['armv7', 'armv7s'],
+            ['armv7', 'armv7s', 'arm64']))
+  else:
+    XCODE_ARCHS_DEFAULT_CACHE = XcodeArchsDefault(
+        '$(ARCHS_STANDARD)',
+        XcodeArchsVariableMapping(['x86_64'], ['x86_64']),
+        XcodeArchsVariableMapping(['i386', 'x86_64'], ['i386', 'x86_64']),
+        XcodeArchsVariableMapping(
+            ['armv7', 'armv7s', 'arm64'],
+            ['armv7', 'armv7s', 'arm64']))
+  return XCODE_ARCHS_DEFAULT_CACHE
+
+
 class XcodeSettings(object):
   """A class that understands the gyp 'xcode_settings' object."""
 
   # Populated lazily by _SdkPath(). Shared by all XcodeSettings, so cached
   # at class-level for efficiency.
   _sdk_path_cache = {}
+  _sdk_root_cache = {}
 
   # Populated lazily by GetExtraPlistItems(). Shared by all XcodeSettings, so
   # cached at class-level for efficiency.
@@ -33,10 +157,6 @@ class XcodeSettings(object):
   # cached at class-level for efficiency.
   _codesigning_key_cache = {}
 
-  # Populated lazily by _XcodeVersion.  Shared by all XcodeSettings, so cached
-  # at class-level for efficiency.
-  _xcode_version_cache = ()
-
   def __init__(self, spec):
     self.spec = spec
 
@@ -266,20 +386,22 @@ class XcodeSettings(object):
 
   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', [self._DefaultArch()])
-
-  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 %s' % (job.returncode, cmdlist[0]))
-    return out.rstrip('\n')
+    config_settings = self.xcode_settings[configname]
+    xcode_archs_default = GetXcodeArchsDefault()
+    return xcode_archs_default.ActiveArchs(
+        config_settings.get('ARCHS'),
+        config_settings.get('VALID_ARCHS'),
+        config_settings.get('SDKROOT'))
 
   def _GetSdkVersionInfoItem(self, sdk, infoitem):
-    return self._GetStdout(['xcodebuild', '-version', '-sdk', sdk, infoitem])
+    # xcodebuild requires Xcode and can't run on Command Line Tools-only
+    # systems from 10.7 onward.
+    # Since the CLT has no SDK paths anyway, returning None is the
+    # most sensible route and should still do the right thing.
+    try:
+      return GetStdout(['xcodebuild', '-version', '-sdk', sdk, infoitem])
+    except:
+      pass
 
   def _SdkRoot(self, configname):
     if configname is None:
@@ -290,9 +412,14 @@ class XcodeSettings(object):
     sdk_root = self._SdkRoot(configname)
     if sdk_root.startswith('/'):
       return sdk_root
+    return self._XcodeSdkPath(sdk_root)
+
+  def _XcodeSdkPath(self, sdk_root):
     if sdk_root not in XcodeSettings._sdk_path_cache:
-      XcodeSettings._sdk_path_cache[sdk_root] = self._GetSdkVersionInfoItem(
-          sdk_root, 'Path')
+      sdk_path = self._GetSdkVersionInfoItem(sdk_root, 'Path')
+      XcodeSettings._sdk_path_cache[sdk_root] = sdk_path
+      if sdk_root:
+        XcodeSettings._sdk_root_cache[sdk_path] = sdk_root
     return XcodeSettings._sdk_path_cache[sdk_root]
 
   def _AppendPlatformVersionMinFlags(self, lst):
@@ -317,7 +444,7 @@ class XcodeSettings(object):
     cflags = []
 
     sdk_root = self._SdkPath()
-    if 'SDKROOT' in self._Settings():
+    if 'SDKROOT' in self._Settings() and sdk_root:
       cflags.append('-isysroot %s' % sdk_root)
 
     if self._Test('CLANG_WARN_CONSTANT_CONVERSION', 'YES', default='NO'):
@@ -383,7 +510,8 @@ class XcodeSettings(object):
     if arch is not None:
       archs = [arch]
     else:
-      archs = self._Settings().get('ARCHS', [self._DefaultArch()])
+      assert self.configname
+      archs = self.GetActiveArchs(self.configname)
     if len(archs) != 1:
       # TODO: Supporting fat binaries will be annoying.
       self._WarnUnimplemented('ARCHS')
@@ -403,10 +531,14 @@ class XcodeSettings(object):
 
     cflags += self._Settings().get('WARNING_CFLAGS', [])
 
+    if sdk_root:
+      framework_root = sdk_root
+    else:
+      framework_root = ''
     config = self.spec['configurations'][self.configname]
     framework_dirs = config.get('mac_framework_dirs', [])
     for directory in framework_dirs:
-      cflags.append('-F' + directory.replace('$(SDKROOT)', sdk_root))
+      cflags.append('-F' + directory.replace('$(SDKROOT)', framework_root))
 
     self.configname = None
     return cflags
@@ -622,7 +754,7 @@ class XcodeSettings(object):
 
     self._AppendPlatformVersionMinFlags(ldflags)
 
-    if 'SDKROOT' in self._Settings():
+    if 'SDKROOT' in self._Settings() and self._SdkPath():
       ldflags.append('-isysroot ' + self._SdkPath())
 
     for library_path in self._Settings().get('LIBRARY_SEARCH_PATHS', []):
@@ -636,7 +768,8 @@ class XcodeSettings(object):
     if arch is not None:
       archs = [arch]
     else:
-      archs = self._Settings().get('ARCHS', [self._DefaultArch()])
+      assert self.configname
+      archs = self.GetActiveArchs(self.configname)
     if len(archs) != 1:
       # TODO: Supporting fat binaries will be annoying.
       self._WarnUnimplemented('ARCHS')
@@ -653,10 +786,13 @@ class XcodeSettings(object):
     for rpath in self._Settings().get('LD_RUNPATH_SEARCH_PATHS', []):
       ldflags.append('-Wl,-rpath,' + rpath)
 
+    sdk_root = self._SdkPath()
+    if not sdk_root:
+      sdk_root = ''
     config = self.spec['configurations'][self.configname]
     framework_dirs = config.get('mac_framework_dirs', [])
     for directory in framework_dirs:
-      ldflags.append('-F' + directory.replace('$(SDKROOT)', self._SdkPath()))
+      ldflags.append('-F' + directory.replace('$(SDKROOT)', sdk_root))
 
     self.configname = None
     return ldflags
@@ -814,9 +950,11 @@ class XcodeSettings(object):
           ['security', 'find-identity', '-p', 'codesigning', '-v'])
       for line in output.splitlines():
         if identity in line:
-          assert identity not in XcodeSettings._codesigning_key_cache, (
-              "Multiple codesigning identities for identity: %s" % identity)
-          XcodeSettings._codesigning_key_cache[identity] = line.split()[1]
+          fingerprint = line.split()[1]
+          cache = XcodeSettings._codesigning_key_cache
+          assert identity not in cache or fingerprint == cache[identity], (
+              "Multiple codesigning fingerprints for identity: %s" % identity)
+          XcodeSettings._codesigning_key_cache[identity] = fingerprint
     return XcodeSettings._codesigning_key_cache.get(identity, '')
 
   def AddImplicitPostbuilds(self, configname, output, output_binary,
@@ -837,7 +975,11 @@ class XcodeSettings(object):
         l = '-l' + m.group(1)
       else:
         l = library
-    return l.replace('$(SDKROOT)', self._SdkPath(config_name))
+
+    sdk_root = self._SdkPath(config_name)
+    if not sdk_root:
+      sdk_root = ''
+    return l.replace('$(SDKROOT)', sdk_root)
 
   def AdjustLibraries(self, libraries, config_name=None):
     """Transforms entries like 'Cocoa.framework' in libraries into entries like
@@ -848,27 +990,7 @@ class XcodeSettings(object):
     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'.
-    if len(XcodeSettings._xcode_version_cache) == 0:
-      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]
-      XcodeSettings._xcode_version_cache = (version, build)
-    return XcodeSettings._xcode_version_cache
+    return GetStdout(['sw_vers', '-buildVersion'])
 
   def _XcodeIOSDeviceFamily(self, configname):
     family = self.xcode_settings[configname].get('TARGETED_DEVICE_FAMILY', '1')
@@ -880,11 +1002,13 @@ class XcodeSettings(object):
       cache = {}
       cache['BuildMachineOSBuild'] = self._BuildMachineOSBuild()
 
-      xcode, xcode_build = self._XcodeVersion()
+      xcode, xcode_build = XcodeVersion()
       cache['DTXcode'] = xcode
       cache['DTXcodeBuild'] = xcode_build
 
       sdk_root = self._SdkRoot(configname)
+      if not sdk_root:
+        sdk_root = self._DefaultSdkRoot()
       cache['DTSDKName'] = sdk_root
       if xcode >= '0430':
         cache['DTSDKBuild'] = self._GetSdkVersionInfoItem(
@@ -909,13 +1033,34 @@ class XcodeSettings(object):
       items['UIDeviceFamily'] = self._XcodeIOSDeviceFamily(configname)
     return items
 
-  def _DefaultArch(self):
-    # The default value for ARCHS changed from ['i386'] to ['x86_64'] in
-    # Xcode 5.
-    version, build = self._XcodeVersion()
-    if version >= '0500':
-      return 'x86_64'
-    return 'i386'
+  def _DefaultSdkRoot(self):
+    """Returns the default SDKROOT to use.
+
+    Prior to version 5.0.0, if SDKROOT was not explicitly set in the Xcode
+    project, then the environment variable was empty. Starting with this
+    version, Xcode uses the name of the newest SDK installed.
+    """
+    xcode_version, xcode_build = XcodeVersion()
+    if xcode_version < '0500':
+      return ''
+    default_sdk_path = self._XcodeSdkPath('')
+    default_sdk_root = XcodeSettings._sdk_root_cache.get(default_sdk_path)
+    if default_sdk_root:
+      return default_sdk_root
+    try:
+      all_sdks = GetStdout(['xcodebuild', '-showsdks'])
+    except:
+      # If xcodebuild fails, there will be no valid SDKs
+      return ''
+    for line in all_sdks.splitlines():
+      items = line.split()
+      if len(items) >= 3 and items[-2] == '-sdk':
+        sdk_root = items[-1]
+        sdk_path = self._XcodeSdkPath(sdk_root)
+        if sdk_path == default_sdk_path:
+          return sdk_root
+    return ''
+
 
 class MacPrefixHeader(object):
   """A class that helps with emulating Xcode's GCC_PREFIX_HEADER feature.
@@ -1023,6 +1168,81 @@ class MacPrefixHeader(object):
     ]
 
 
+def XcodeVersion():
+  """Returns a tuple of version and build version of installed Xcode."""
+  # `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'.
+  global XCODE_VERSION_CACHE
+  if XCODE_VERSION_CACHE:
+    return XCODE_VERSION_CACHE
+  try:
+    version_list = GetStdout(['xcodebuild', '-version']).splitlines()
+    # In some circumstances xcodebuild exits 0 but doesn't return
+    # the right results; for example, a user on 10.7 or 10.8 with
+    # a bogus path set via xcode-select
+    # In that case this may be a CLT-only install so fall back to
+    # checking that version.
+    if len(version_list) < 2:
+      raise GypError, "xcodebuild returned unexpected results"
+  except:
+    version = CLTVersion()
+    if version:
+      version = re.match('(\d\.\d\.?\d*)', version).groups()[0]
+    else:
+      raise GypError, "No Xcode or CLT version detected!"
+    # The CLT has no build information, so we return an empty string.
+    version_list = [version, '']
+  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)
+  if build:
+    build = build.split()[-1]
+  XCODE_VERSION_CACHE = (version, build)
+  return XCODE_VERSION_CACHE
+
+
+# This function ported from the logic in Homebrew's CLT version check
+def CLTVersion():
+  """Returns the version of command-line tools from pkgutil."""
+  # pkgutil output looks like
+  #   package-id: com.apple.pkg.CLTools_Executables
+  #   version: 5.0.1.0.1.1382131676
+  #   volume: /
+  #   location: /
+  #   install-time: 1382544035
+  #   groups: com.apple.FindSystemFiles.pkg-group com.apple.DevToolsBoth.pkg-group com.apple.DevToolsNonRelocatableShared.pkg-group
+  STANDALONE_PKG_ID = "com.apple.pkg.DeveloperToolsCLILeo"
+  FROM_XCODE_PKG_ID = "com.apple.pkg.DeveloperToolsCLI"
+  MAVERICKS_PKG_ID = "com.apple.pkg.CLTools_Executables"
+
+  regex = re.compile('version: (?P<version>.+)')
+  for key in [MAVERICKS_PKG_ID, STANDALONE_PKG_ID, FROM_XCODE_PKG_ID]:
+    try:
+      output = GetStdout(['/usr/sbin/pkgutil', '--pkg-info', key])
+      return re.search(regex, output).groupdict()['version']
+    except:
+      continue
+
+
+def GetStdout(cmdlist):
+  """Returns the content of standard output returned by invoking |cmdlist|.
+  Raises |GypError| if the command return with a non-zero return code."""
+  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 %s' % (job.returncode, cmdlist[0]))
+  return out.rstrip('\n')
+
+
 def MergeGlobalXcodeSettingsToSpec(global_dict, spec):
   """Merges the global xcode_settings dictionary into each configuration of the
   target represented by spec. For keys that are both in the global and the local
@@ -1202,6 +1422,11 @@ def _GetXcodeEnv(xcode_settings, built_products_dir, srcroot, configuration,
   install_name_base = xcode_settings.GetInstallNameBase()
   if install_name_base:
     env['DYLIB_INSTALL_NAME_BASE'] = install_name_base
+  if XcodeVersion() >= '0500' and not env.get('SDKROOT'):
+    sdk_root = xcode_settings._SdkRoot(configuration)
+    if not sdk_root:
+      sdk_root = xcode_settings._XcodeSdkPath('')
+    env['SDKROOT'] = sdk_root
 
   if not additional_settings:
     additional_settings = {}
@@ -1312,16 +1537,16 @@ def _HasIOSTarget(targets):
 
 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
+  to build for iOS devices and use correct architectures for those builds."""
+  for target_dict in targets.itervalues():
+    toolset = target_dict['toolset']
+    configs = target_dict['configurations']
+    for config_name, config_dict in dict(configs).iteritems():
+      iphoneos_config_dict = copy.deepcopy(config_dict)
+      configs[config_name + '-iphoneos'] = iphoneos_config_dict
+      configs[config_name + '-iphonesimulator'] = config_dict
+      if toolset == 'target':
+        iphoneos_config_dict['xcode_settings']['SDKROOT'] = 'iphoneos'
   return targets
 
 def CloneConfigurationForDeviceAndEmulator(target_dicts):