(root)/
Python-3.11.7/
Lib/
_osx_support.py
       1  """Shared OS X support functions."""
       2  
       3  import os
       4  import re
       5  import sys
       6  
       7  __all__ = [
       8      'compiler_fixup',
       9      'customize_config_vars',
      10      'customize_compiler',
      11      'get_platform_osx',
      12  ]
      13  
      14  # configuration variables that may contain universal build flags,
      15  # like "-arch" or "-isdkroot", that may need customization for
      16  # the user environment
      17  _UNIVERSAL_CONFIG_VARS = ('CFLAGS', 'LDFLAGS', 'CPPFLAGS', 'BASECFLAGS',
      18                              'BLDSHARED', 'LDSHARED', 'CC', 'CXX',
      19                              'PY_CFLAGS', 'PY_LDFLAGS', 'PY_CPPFLAGS',
      20                              'PY_CORE_CFLAGS', 'PY_CORE_LDFLAGS')
      21  
      22  # configuration variables that may contain compiler calls
      23  _COMPILER_CONFIG_VARS = ('BLDSHARED', 'LDSHARED', 'CC', 'CXX')
      24  
      25  # prefix added to original configuration variable names
      26  _INITPRE = '_OSX_SUPPORT_INITIAL_'
      27  
      28  
      29  def _find_executable(executable, path=None):
      30      """Tries to find 'executable' in the directories listed in 'path'.
      31  
      32      A string listing directories separated by 'os.pathsep'; defaults to
      33      os.environ['PATH'].  Returns the complete filename or None if not found.
      34      """
      35      if path is None:
      36          path = os.environ['PATH']
      37  
      38      paths = path.split(os.pathsep)
      39      base, ext = os.path.splitext(executable)
      40  
      41      if (sys.platform == 'win32') and (ext != '.exe'):
      42          executable = executable + '.exe'
      43  
      44      if not os.path.isfile(executable):
      45          for p in paths:
      46              f = os.path.join(p, executable)
      47              if os.path.isfile(f):
      48                  # the file exists, we have a shot at spawn working
      49                  return f
      50          return None
      51      else:
      52          return executable
      53  
      54  
      55  def _read_output(commandstring, capture_stderr=False):
      56      """Output from successful command execution or None"""
      57      # Similar to os.popen(commandstring, "r").read(),
      58      # but without actually using os.popen because that
      59      # function is not usable during python bootstrap.
      60      # tempfile is also not available then.
      61      import contextlib
      62      try:
      63          import tempfile
      64          fp = tempfile.NamedTemporaryFile()
      65      except ImportError:
      66          fp = open("/tmp/_osx_support.%s"%(
      67              os.getpid(),), "w+b")
      68  
      69      with contextlib.closing(fp) as fp:
      70          if capture_stderr:
      71              cmd = "%s >'%s' 2>&1" % (commandstring, fp.name)
      72          else:
      73              cmd = "%s 2>/dev/null >'%s'" % (commandstring, fp.name)
      74          return fp.read().decode('utf-8').strip() if not os.system(cmd) else None
      75  
      76  
      77  def _find_build_tool(toolname):
      78      """Find a build tool on current path or using xcrun"""
      79      return (_find_executable(toolname)
      80                  or _read_output("/usr/bin/xcrun -find %s" % (toolname,))
      81                  or ''
      82              )
      83  
      84  _SYSTEM_VERSION = None
      85  
      86  def _get_system_version():
      87      """Return the OS X system version as a string"""
      88      # Reading this plist is a documented way to get the system
      89      # version (see the documentation for the Gestalt Manager)
      90      # We avoid using platform.mac_ver to avoid possible bootstrap issues during
      91      # the build of Python itself (distutils is used to build standard library
      92      # extensions).
      93  
      94      global _SYSTEM_VERSION
      95  
      96      if _SYSTEM_VERSION is None:
      97          _SYSTEM_VERSION = ''
      98          try:
      99              f = open('/System/Library/CoreServices/SystemVersion.plist', encoding="utf-8")
     100          except OSError:
     101              # We're on a plain darwin box, fall back to the default
     102              # behaviour.
     103              pass
     104          else:
     105              try:
     106                  m = re.search(r'<key>ProductUserVisibleVersion</key>\s*'
     107                                r'<string>(.*?)</string>', f.read())
     108              finally:
     109                  f.close()
     110              if m is not None:
     111                  _SYSTEM_VERSION = '.'.join(m.group(1).split('.')[:2])
     112              # else: fall back to the default behaviour
     113  
     114      return _SYSTEM_VERSION
     115  
     116  _SYSTEM_VERSION_TUPLE = None
     117  def _get_system_version_tuple():
     118      """
     119      Return the macOS system version as a tuple
     120  
     121      The return value is safe to use to compare
     122      two version numbers.
     123      """
     124      global _SYSTEM_VERSION_TUPLE
     125      if _SYSTEM_VERSION_TUPLE is None:
     126          osx_version = _get_system_version()
     127          if osx_version:
     128              try:
     129                  _SYSTEM_VERSION_TUPLE = tuple(int(i) for i in osx_version.split('.'))
     130              except ValueError:
     131                  _SYSTEM_VERSION_TUPLE = ()
     132  
     133      return _SYSTEM_VERSION_TUPLE
     134  
     135  
     136  def _remove_original_values(_config_vars):
     137      """Remove original unmodified values for testing"""
     138      # This is needed for higher-level cross-platform tests of get_platform.
     139      for k in list(_config_vars):
     140          if k.startswith(_INITPRE):
     141              del _config_vars[k]
     142  
     143  def _save_modified_value(_config_vars, cv, newvalue):
     144      """Save modified and original unmodified value of configuration var"""
     145  
     146      oldvalue = _config_vars.get(cv, '')
     147      if (oldvalue != newvalue) and (_INITPRE + cv not in _config_vars):
     148          _config_vars[_INITPRE + cv] = oldvalue
     149      _config_vars[cv] = newvalue
     150  
     151  
     152  _cache_default_sysroot = None
     153  def _default_sysroot(cc):
     154      """ Returns the root of the default SDK for this system, or '/' """
     155      global _cache_default_sysroot
     156  
     157      if _cache_default_sysroot is not None:
     158          return _cache_default_sysroot
     159  
     160      contents = _read_output('%s -c -E -v - </dev/null' % (cc,), True)
     161      in_incdirs = False
     162      for line in contents.splitlines():
     163          if line.startswith("#include <...>"):
     164              in_incdirs = True
     165          elif line.startswith("End of search list"):
     166              in_incdirs = False
     167          elif in_incdirs:
     168              line = line.strip()
     169              if line == '/usr/include':
     170                  _cache_default_sysroot = '/'
     171              elif line.endswith(".sdk/usr/include"):
     172                  _cache_default_sysroot = line[:-12]
     173      if _cache_default_sysroot is None:
     174          _cache_default_sysroot = '/'
     175  
     176      return _cache_default_sysroot
     177  
     178  def _supports_universal_builds():
     179      """Returns True if universal builds are supported on this system"""
     180      # As an approximation, we assume that if we are running on 10.4 or above,
     181      # then we are running with an Xcode environment that supports universal
     182      # builds, in particular -isysroot and -arch arguments to the compiler. This
     183      # is in support of allowing 10.4 universal builds to run on 10.3.x systems.
     184  
     185      osx_version = _get_system_version_tuple()
     186      return bool(osx_version >= (10, 4)) if osx_version else False
     187  
     188  def _supports_arm64_builds():
     189      """Returns True if arm64 builds are supported on this system"""
     190      # There are two sets of systems supporting macOS/arm64 builds:
     191      # 1. macOS 11 and later, unconditionally
     192      # 2. macOS 10.15 with Xcode 12.2 or later
     193      # For now the second category is ignored.
     194      osx_version = _get_system_version_tuple()
     195      return osx_version >= (11, 0) if osx_version else False
     196  
     197  
     198  def _find_appropriate_compiler(_config_vars):
     199      """Find appropriate C compiler for extension module builds"""
     200  
     201      # Issue #13590:
     202      #    The OSX location for the compiler varies between OSX
     203      #    (or rather Xcode) releases.  With older releases (up-to 10.5)
     204      #    the compiler is in /usr/bin, with newer releases the compiler
     205      #    can only be found inside Xcode.app if the "Command Line Tools"
     206      #    are not installed.
     207      #
     208      #    Furthermore, the compiler that can be used varies between
     209      #    Xcode releases. Up to Xcode 4 it was possible to use 'gcc-4.2'
     210      #    as the compiler, after that 'clang' should be used because
     211      #    gcc-4.2 is either not present, or a copy of 'llvm-gcc' that
     212      #    miscompiles Python.
     213  
     214      # skip checks if the compiler was overridden with a CC env variable
     215      if 'CC' in os.environ:
     216          return _config_vars
     217  
     218      # The CC config var might contain additional arguments.
     219      # Ignore them while searching.
     220      cc = oldcc = _config_vars['CC'].split()[0]
     221      if not _find_executable(cc):
     222          # Compiler is not found on the shell search PATH.
     223          # Now search for clang, first on PATH (if the Command LIne
     224          # Tools have been installed in / or if the user has provided
     225          # another location via CC).  If not found, try using xcrun
     226          # to find an uninstalled clang (within a selected Xcode).
     227  
     228          # NOTE: Cannot use subprocess here because of bootstrap
     229          # issues when building Python itself (and os.popen is
     230          # implemented on top of subprocess and is therefore not
     231          # usable as well)
     232  
     233          cc = _find_build_tool('clang')
     234  
     235      elif os.path.basename(cc).startswith('gcc'):
     236          # Compiler is GCC, check if it is LLVM-GCC
     237          data = _read_output("'%s' --version"
     238                               % (cc.replace("'", "'\"'\"'"),))
     239          if data and 'llvm-gcc' in data:
     240              # Found LLVM-GCC, fall back to clang
     241              cc = _find_build_tool('clang')
     242  
     243      if not cc:
     244          raise SystemError(
     245                 "Cannot locate working compiler")
     246  
     247      if cc != oldcc:
     248          # Found a replacement compiler.
     249          # Modify config vars using new compiler, if not already explicitly
     250          # overridden by an env variable, preserving additional arguments.
     251          for cv in _COMPILER_CONFIG_VARS:
     252              if cv in _config_vars and cv not in os.environ:
     253                  cv_split = _config_vars[cv].split()
     254                  cv_split[0] = cc if cv != 'CXX' else cc + '++'
     255                  _save_modified_value(_config_vars, cv, ' '.join(cv_split))
     256  
     257      return _config_vars
     258  
     259  
     260  def _remove_universal_flags(_config_vars):
     261      """Remove all universal build arguments from config vars"""
     262  
     263      for cv in _UNIVERSAL_CONFIG_VARS:
     264          # Do not alter a config var explicitly overridden by env var
     265          if cv in _config_vars and cv not in os.environ:
     266              flags = _config_vars[cv]
     267              flags = re.sub(r'-arch\s+\w+\s', ' ', flags, flags=re.ASCII)
     268              flags = re.sub(r'-isysroot\s*\S+', ' ', flags)
     269              _save_modified_value(_config_vars, cv, flags)
     270  
     271      return _config_vars
     272  
     273  
     274  def _remove_unsupported_archs(_config_vars):
     275      """Remove any unsupported archs from config vars"""
     276      # Different Xcode releases support different sets for '-arch'
     277      # flags. In particular, Xcode 4.x no longer supports the
     278      # PPC architectures.
     279      #
     280      # This code automatically removes '-arch ppc' and '-arch ppc64'
     281      # when these are not supported. That makes it possible to
     282      # build extensions on OSX 10.7 and later with the prebuilt
     283      # 32-bit installer on the python.org website.
     284  
     285      # skip checks if the compiler was overridden with a CC env variable
     286      if 'CC' in os.environ:
     287          return _config_vars
     288  
     289      if re.search(r'-arch\s+ppc', _config_vars['CFLAGS']) is not None:
     290          # NOTE: Cannot use subprocess here because of bootstrap
     291          # issues when building Python itself
     292          status = os.system(
     293              """echo 'int main{};' | """
     294              """'%s' -c -arch ppc -x c -o /dev/null /dev/null 2>/dev/null"""
     295              %(_config_vars['CC'].replace("'", "'\"'\"'"),))
     296          if status:
     297              # The compile failed for some reason.  Because of differences
     298              # across Xcode and compiler versions, there is no reliable way
     299              # to be sure why it failed.  Assume here it was due to lack of
     300              # PPC support and remove the related '-arch' flags from each
     301              # config variables not explicitly overridden by an environment
     302              # variable.  If the error was for some other reason, we hope the
     303              # failure will show up again when trying to compile an extension
     304              # module.
     305              for cv in _UNIVERSAL_CONFIG_VARS:
     306                  if cv in _config_vars and cv not in os.environ:
     307                      flags = _config_vars[cv]
     308                      flags = re.sub(r'-arch\s+ppc\w*\s', ' ', flags)
     309                      _save_modified_value(_config_vars, cv, flags)
     310  
     311      return _config_vars
     312  
     313  
     314  def _override_all_archs(_config_vars):
     315      """Allow override of all archs with ARCHFLAGS env var"""
     316      # NOTE: This name was introduced by Apple in OSX 10.5 and
     317      # is used by several scripting languages distributed with
     318      # that OS release.
     319      if 'ARCHFLAGS' in os.environ:
     320          arch = os.environ['ARCHFLAGS']
     321          for cv in _UNIVERSAL_CONFIG_VARS:
     322              if cv in _config_vars and '-arch' in _config_vars[cv]:
     323                  flags = _config_vars[cv]
     324                  flags = re.sub(r'-arch\s+\w+\s', ' ', flags)
     325                  flags = flags + ' ' + arch
     326                  _save_modified_value(_config_vars, cv, flags)
     327  
     328      return _config_vars
     329  
     330  
     331  def _check_for_unavailable_sdk(_config_vars):
     332      """Remove references to any SDKs not available"""
     333      # If we're on OSX 10.5 or later and the user tries to
     334      # compile an extension using an SDK that is not present
     335      # on the current machine it is better to not use an SDK
     336      # than to fail.  This is particularly important with
     337      # the standalone Command Line Tools alternative to a
     338      # full-blown Xcode install since the CLT packages do not
     339      # provide SDKs.  If the SDK is not present, it is assumed
     340      # that the header files and dev libs have been installed
     341      # to /usr and /System/Library by either a standalone CLT
     342      # package or the CLT component within Xcode.
     343      cflags = _config_vars.get('CFLAGS', '')
     344      m = re.search(r'-isysroot\s*(\S+)', cflags)
     345      if m is not None:
     346          sdk = m.group(1)
     347          if not os.path.exists(sdk):
     348              for cv in _UNIVERSAL_CONFIG_VARS:
     349                  # Do not alter a config var explicitly overridden by env var
     350                  if cv in _config_vars and cv not in os.environ:
     351                      flags = _config_vars[cv]
     352                      flags = re.sub(r'-isysroot\s*\S+(?:\s|$)', ' ', flags)
     353                      _save_modified_value(_config_vars, cv, flags)
     354  
     355      return _config_vars
     356  
     357  
     358  def compiler_fixup(compiler_so, cc_args):
     359      """
     360      This function will strip '-isysroot PATH' and '-arch ARCH' from the
     361      compile flags if the user has specified one them in extra_compile_flags.
     362  
     363      This is needed because '-arch ARCH' adds another architecture to the
     364      build, without a way to remove an architecture. Furthermore GCC will
     365      barf if multiple '-isysroot' arguments are present.
     366      """
     367      stripArch = stripSysroot = False
     368  
     369      compiler_so = list(compiler_so)
     370  
     371      if not _supports_universal_builds():
     372          # OSX before 10.4.0, these don't support -arch and -isysroot at
     373          # all.
     374          stripArch = stripSysroot = True
     375      else:
     376          stripArch = '-arch' in cc_args
     377          stripSysroot = any(arg for arg in cc_args if arg.startswith('-isysroot'))
     378  
     379      if stripArch or 'ARCHFLAGS' in os.environ:
     380          while True:
     381              try:
     382                  index = compiler_so.index('-arch')
     383                  # Strip this argument and the next one:
     384                  del compiler_so[index:index+2]
     385              except ValueError:
     386                  break
     387  
     388      elif not _supports_arm64_builds():
     389          # Look for "-arch arm64" and drop that
     390          for idx in reversed(range(len(compiler_so))):
     391              if compiler_so[idx] == '-arch' and compiler_so[idx+1] == "arm64":
     392                  del compiler_so[idx:idx+2]
     393  
     394      if 'ARCHFLAGS' in os.environ and not stripArch:
     395          # User specified different -arch flags in the environ,
     396          # see also distutils.sysconfig
     397          compiler_so = compiler_so + os.environ['ARCHFLAGS'].split()
     398  
     399      if stripSysroot:
     400          while True:
     401              indices = [i for i,x in enumerate(compiler_so) if x.startswith('-isysroot')]
     402              if not indices:
     403                  break
     404              index = indices[0]
     405              if compiler_so[index] == '-isysroot':
     406                  # Strip this argument and the next one:
     407                  del compiler_so[index:index+2]
     408              else:
     409                  # It's '-isysroot/some/path' in one arg
     410                  del compiler_so[index:index+1]
     411  
     412      # Check if the SDK that is used during compilation actually exists,
     413      # the universal build requires the usage of a universal SDK and not all
     414      # users have that installed by default.
     415      sysroot = None
     416      argvar = cc_args
     417      indices = [i for i,x in enumerate(cc_args) if x.startswith('-isysroot')]
     418      if not indices:
     419          argvar = compiler_so
     420          indices = [i for i,x in enumerate(compiler_so) if x.startswith('-isysroot')]
     421  
     422      for idx in indices:
     423          if argvar[idx] == '-isysroot':
     424              sysroot = argvar[idx+1]
     425              break
     426          else:
     427              sysroot = argvar[idx][len('-isysroot'):]
     428              break
     429  
     430      if sysroot and not os.path.isdir(sysroot):
     431          sys.stderr.write(f"Compiling with an SDK that doesn't seem to exist: {sysroot}\n")
     432          sys.stderr.write("Please check your Xcode installation\n")
     433          sys.stderr.flush()
     434  
     435      return compiler_so
     436  
     437  
     438  def customize_config_vars(_config_vars):
     439      """Customize Python build configuration variables.
     440  
     441      Called internally from sysconfig with a mutable mapping
     442      containing name/value pairs parsed from the configured
     443      makefile used to build this interpreter.  Returns
     444      the mapping updated as needed to reflect the environment
     445      in which the interpreter is running; in the case of
     446      a Python from a binary installer, the installed
     447      environment may be very different from the build
     448      environment, i.e. different OS levels, different
     449      built tools, different available CPU architectures.
     450  
     451      This customization is performed whenever
     452      distutils.sysconfig.get_config_vars() is first
     453      called.  It may be used in environments where no
     454      compilers are present, i.e. when installing pure
     455      Python dists.  Customization of compiler paths
     456      and detection of unavailable archs is deferred
     457      until the first extension module build is
     458      requested (in distutils.sysconfig.customize_compiler).
     459  
     460      Currently called from distutils.sysconfig
     461      """
     462  
     463      if not _supports_universal_builds():
     464          # On Mac OS X before 10.4, check if -arch and -isysroot
     465          # are in CFLAGS or LDFLAGS and remove them if they are.
     466          # This is needed when building extensions on a 10.3 system
     467          # using a universal build of python.
     468          _remove_universal_flags(_config_vars)
     469  
     470      # Allow user to override all archs with ARCHFLAGS env var
     471      _override_all_archs(_config_vars)
     472  
     473      # Remove references to sdks that are not found
     474      _check_for_unavailable_sdk(_config_vars)
     475  
     476      return _config_vars
     477  
     478  
     479  def customize_compiler(_config_vars):
     480      """Customize compiler path and configuration variables.
     481  
     482      This customization is performed when the first
     483      extension module build is requested
     484      in distutils.sysconfig.customize_compiler.
     485      """
     486  
     487      # Find a compiler to use for extension module builds
     488      _find_appropriate_compiler(_config_vars)
     489  
     490      # Remove ppc arch flags if not supported here
     491      _remove_unsupported_archs(_config_vars)
     492  
     493      # Allow user to override all archs with ARCHFLAGS env var
     494      _override_all_archs(_config_vars)
     495  
     496      return _config_vars
     497  
     498  
     499  def get_platform_osx(_config_vars, osname, release, machine):
     500      """Filter values for get_platform()"""
     501      # called from get_platform() in sysconfig and distutils.util
     502      #
     503      # For our purposes, we'll assume that the system version from
     504      # distutils' perspective is what MACOSX_DEPLOYMENT_TARGET is set
     505      # to. This makes the compatibility story a bit more sane because the
     506      # machine is going to compile and link as if it were
     507      # MACOSX_DEPLOYMENT_TARGET.
     508  
     509      macver = _config_vars.get('MACOSX_DEPLOYMENT_TARGET', '')
     510      macrelease = _get_system_version() or macver
     511      macver = macver or macrelease
     512  
     513      if macver:
     514          release = macver
     515          osname = "macosx"
     516  
     517          # Use the original CFLAGS value, if available, so that we
     518          # return the same machine type for the platform string.
     519          # Otherwise, distutils may consider this a cross-compiling
     520          # case and disallow installs.
     521          cflags = _config_vars.get(_INITPRE+'CFLAGS',
     522                                      _config_vars.get('CFLAGS', ''))
     523          if macrelease:
     524              try:
     525                  macrelease = tuple(int(i) for i in macrelease.split('.')[0:2])
     526              except ValueError:
     527                  macrelease = (10, 3)
     528          else:
     529              # assume no universal support
     530              macrelease = (10, 3)
     531  
     532          if (macrelease >= (10, 4)) and '-arch' in cflags.strip():
     533              # The universal build will build fat binaries, but not on
     534              # systems before 10.4
     535  
     536              machine = 'fat'
     537  
     538              archs = re.findall(r'-arch\s+(\S+)', cflags)
     539              archs = tuple(sorted(set(archs)))
     540  
     541              if len(archs) == 1:
     542                  machine = archs[0]
     543              elif archs == ('arm64', 'x86_64'):
     544                  machine = 'universal2'
     545              elif archs == ('i386', 'ppc'):
     546                  machine = 'fat'
     547              elif archs == ('i386', 'x86_64'):
     548                  machine = 'intel'
     549              elif archs == ('i386', 'ppc', 'x86_64'):
     550                  machine = 'fat3'
     551              elif archs == ('ppc64', 'x86_64'):
     552                  machine = 'fat64'
     553              elif archs == ('i386', 'ppc', 'ppc64', 'x86_64'):
     554                  machine = 'universal'
     555              else:
     556                  raise ValueError(
     557                     "Don't know machine value for archs=%r" % (archs,))
     558  
     559          elif machine == 'i386':
     560              # On OSX the machine type returned by uname is always the
     561              # 32-bit variant, even if the executable architecture is
     562              # the 64-bit variant
     563              if sys.maxsize >= 2**32:
     564                  machine = 'x86_64'
     565  
     566          elif machine in ('PowerPC', 'Power_Macintosh'):
     567              # Pick a sane name for the PPC architecture.
     568              # See 'i386' case
     569              if sys.maxsize >= 2**32:
     570                  machine = 'ppc64'
     571              else:
     572                  machine = 'ppc'
     573  
     574      return (osname, release, machine)