text: import platform
  import subprocess
  import sys
  import textwrap
  from os import PathLike
  from pathlib import Path, PurePath
  from typing import List, NamedTuple, Union
  
  from .docker_container import DockerContainer
  from .util import (BuildOptions, BuildSelector, NonPlatformWheelError,
                     get_build_verbosity_extra_flags, prepare_command)
  
  
  def matches_platform(identifier: str) -> bool:
      pm = platform.machine()
      if pm == "x86_64":
          # x86_64 machines can run i686 docker containers
          if identifier.endswith('x86_64') or identifier.endswith('i686'):
              return True
      elif pm == "i686":
          if identifier.endswith('i686'):
              return True
      elif pm == "aarch64":
          if identifier.endswith('aarch64'):
              return True
      elif pm == "ppc64le":
          if identifier.endswith('ppc64le'):
              return True
      elif pm == "s390x":
          if identifier.endswith('s390x'):
              return True
      return False
  
  
  class PythonConfiguration(NamedTuple):
      version: str
      identifier: str
      path_str: str
  
      @property
      def path(self):
          return PurePath(self.path_str)
  
  
  def get_python_configurations(build_selector: BuildSelector) -> List[PythonConfiguration]:
      python_configurations = [
          PythonConfiguration(version='2.7', identifier='cp27-manylinux_x86_64', path_str='/opt/python/cp27-cp27m'),
          PythonConfiguration(version='2.7', identifier='cp27-manylinux_x86_64', path_str='/opt/python/cp27-cp27mu'),
          PythonConfiguration(version='3.5', identifier='cp35-manylinux_x86_64', path_str='/opt/python/cp35-cp35m'),
          PythonConfiguration(version='3.6', identifier='cp36-manylinux_x86_64', path_str='/opt/python/cp36-cp36m'),
          PythonConfiguration(version='3.7', identifier='cp37-manylinux_x86_64', path_str='/opt/python/cp37-cp37m'),
          PythonConfiguration(version='3.8', identifier='cp38-manylinux_x86_64', path_str='/opt/python/cp38-cp38'),
          PythonConfiguration(version='3.9', identifier='cp39-manylinux_x86_64', path_str='/opt/python/cp39-cp39'),
          PythonConfiguration(version='2.7', identifier='cp27-manylinux_i686', path_str='/opt/python/cp27-cp27m'),
          PythonConfiguration(version='2.7', identifier='cp27-manylinux_i686', path_str='/opt/python/cp27-cp27mu'),
          PythonConfiguration(version='3.5', identifier='cp35-manylinux_i686', path_str='/opt/python/cp35-cp35m'),
          PythonConfiguration(version='3.6', identifier='cp36-manylinux_i686', path_str='/opt/python/cp36-cp36m'),
          PythonConfiguration(version='3.7', identifier='cp37-manylinux_i686', path_str='/opt/python/cp37-cp37m'),
          PythonConfiguration(version='3.8', identifier='cp38-manylinux_i686', path_str='/opt/python/cp38-cp38'),
          PythonConfiguration(version='3.9', identifier='cp39-manylinux_i686', path_str='/opt/python/cp39-cp39'),
          PythonConfiguration(version='2.7', identifier='pp27-manylinux_x86_64', path_str='/opt/python/pp27-pypy_73'),
          PythonConfiguration(version='3.6', identifier='pp36-manylinux_x86_64', path_str='/opt/python/pp36-pypy36_pp73'),
          PythonConfiguration(version='3.5', identifier='cp35-manylinux_aarch64', path_str='/opt/python/cp35-cp35m'),
          PythonConfiguration(version='3.6', identifier='cp36-manylinux_aarch64', path_str='/opt/python/cp36-cp36m'),
          PythonConfiguration(version='3.7', identifier='cp37-manylinux_aarch64', path_str='/opt/python/cp37-cp37m'),
          PythonConfiguration(version='3.8', identifier='cp38-manylinux_aarch64', path_str='/opt/python/cp38-cp38'),
          PythonConfiguration(version='3.9', identifier='cp39-manylinux_aarch64', path_str='/opt/python/cp39-cp39'),
          PythonConfiguration(version='3.5', identifier='cp35-manylinux_ppc64le', path_str='/opt/python/cp35-cp35m'),
          PythonConfiguration(version='3.6', identifier='cp36-manylinux_ppc64le', path_str='/opt/python/cp36-cp36m'),
          PythonConfiguration(version='3.7', identifier='cp37-manylinux_ppc64le', path_str='/opt/python/cp37-cp37m'),
          PythonConfiguration(version='3.8', identifier='cp38-manylinux_ppc64le', path_str='/opt/python/cp38-cp38'),
          PythonConfiguration(version='3.9', identifier='cp39-manylinux_ppc64le', path_str='/opt/python/cp39-cp39'),
          PythonConfiguration(version='3.5', identifier='cp35-manylinux_s390x', path_str='/opt/python/cp35-cp35m'),
          PythonConfiguration(version='3.6', identifier='cp36-manylinux_s390x', path_str='/opt/python/cp36-cp36m'),
          PythonConfiguration(version='3.7', identifier='cp37-manylinux_s390x', path_str='/opt/python/cp37-cp37m'),
          PythonConfiguration(version='3.8', identifier='cp38-manylinux_s390x', path_str='/opt/python/cp38-cp38'),
          PythonConfiguration(version='3.9', identifier='cp39-manylinux_s390x', path_str='/opt/python/cp39-cp39'),
      ]
      # skip builds as required
      return [c for c in python_configurations if matches_platform(c.identifier) and build_selector(c.identifier)]
  
  
  def build(options: BuildOptions) -> None:
      try:
          subprocess.check_call(['docker', '--version'])
      except Exception:
          print('cibuildwheel: Docker not found. Docker is required to run Linux builds. '
                'If you\'re building on Travis CI, add `services: [docker]` to your .travis.yml.'
                'If you\'re building on Circle CI in Linux, add a `setup_remote_docker` step to your .circleci/config.yml',
                file=sys.stderr)
          exit(2)
  
      assert options.manylinux_images is not None
      python_configurations = get_python_configurations(options.build_selector)
      platforms = [
          ('cp', 'manylinux_x86_64', options.manylinux_images['x86_64']),
          ('cp', 'manylinux_i686', options.manylinux_images['i686']),
          ('cp', 'manylinux_aarch64', options.manylinux_images['aarch64']),
          ('cp', 'manylinux_ppc64le', options.manylinux_images['ppc64le']),
          ('cp', 'manylinux_s390x', options.manylinux_images['s390x']),
          ('pp', 'manylinux_x86_64', options.manylinux_images['pypy_x86_64']),
      ]
  
      cwd = Path.cwd()
      abs_package_dir = options.package_dir.resolve()
      if cwd != abs_package_dir and cwd not in abs_package_dir.parents:
          raise Exception('package_dir must be inside the working directory')
  
      container_project_path = PurePath('/project')
      container_package_dir = container_project_path / abs_package_dir.relative_to(cwd)
      container_output_dir = PurePath('/output')
  
      for implementation, platform_tag, docker_image in platforms:
          platform_configs = [c for c in python_configurations if c.identifier.startswith(implementation) and c.identifier.endswith(platform_tag)]
          if not platform_configs:
              continue
  
          try:
              with DockerContainer(docker_image, simulate_32_bit=platform_tag.endswith('i686'), cwd=container_project_path) as docker:
                  docker.copy_into(Path.cwd(), container_project_path)
  
                  if options.before_all:
                      env = docker.get_environment()
                      env['PATH'] = f'/opt/python/cp38-cp38:{env["PATH"]}'
                      env = options.environment.as_dictionary(env, executor=docker.environment_executor)
  
                      before_all_prepared = prepare_command(options.before_all, project=container_project_path, package=container_package_dir)
                      docker.call(['sh', '-c', before_all_prepared], env=env)
  
                  for config in platform_configs:
                      dependency_constraint_flags: List[Union[str, PathLike]] = []
  
                      if options.dependency_constraints:
                          constraints_file = options.dependency_constraints.get_for_python_version(config.version)
                          container_constraints_file = PurePath('/constraints.txt')
  
                          docker.copy_into(constraints_file, container_constraints_file)
                          dependency_constraint_flags = ['-c', container_constraints_file]
  
                      env = docker.get_environment()
  
                      # put this config's python top of the list
                      python_bin = config.path / 'bin'
                      env['PATH'] = f'{python_bin}:{env["PATH"]}'
  
                      env = options.environment.as_dictionary(env, executor=docker.environment_executor)
  
                      # check config python and pip are still on PATH
                      which_python = docker.call(['which', 'python'], env=env, capture_output=True).strip()
                      if PurePath(which_python) != python_bin / 'python':
                          print("cibuildwheel: python available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert python above it.", file=sys.stderr)
                          exit(1)
  
                      which_pip = docker.call(['which', 'pip'], env=env, capture_output=True).strip()
                      if PurePath(which_pip) != python_bin / 'pip':
                          print("cibuildwheel: pip available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert pip above it.", file=sys.stderr)
                          exit(1)
  
                      if options.before_build:
                          before_build_prepared = prepare_command(options.before_build, project=container_project_path, package=container_package_dir)
                          docker.call(['sh', '-c', before_build_prepared], env=env)
  
                      temp_dir = PurePath('/tmp/cibuildwheel')
                      built_wheel_dir = temp_dir / 'built_wheel'
                      docker.call(['rm', '-rf', built_wheel_dir])
                      docker.call(['mkdir', '-p', built_wheel_dir])
  
                      docker.call([
                          'pip', 'wheel',
                          container_package_dir,
                          '-w', built_wheel_dir,
                          '--no-deps',
                          *get_build_verbosity_extra_flags(options.build_verbosity)
                      ], env=env)
  
                      built_wheel = docker.glob(built_wheel_dir, '*.whl')[0]
  
                      repaired_wheel_dir = temp_dir / 'repaired_wheel'
                      docker.call(['rm', '-rf', repaired_wheel_dir])
                      docker.call(['mkdir', '-p', repaired_wheel_dir])
  
                      if built_wheel.name.endswith('none-any.whl'):
                          raise NonPlatformWheelError()
  
                      if options.repair_command:
                          repair_command_prepared = prepare_command(options.repair_command, wheel=built_wheel, dest_dir=repaired_wheel_dir)
                          docker.call(['sh', '-c', repair_command_prepared], env=env)
                      else:
                          docker.call(['mv', built_wheel, repaired_wheel_dir])
  
                      repaired_wheels = docker.glob(repaired_wheel_dir, '*.whl')
  
                      if options.test_command:
                          # set up a virtual environment to install and test from, to make sure
                          # there are no dependencies that were pulled in at build time.
                          docker.call(['pip', 'install', 'virtualenv', *dependency_constraint_flags], env=env)
                          venv_dir = PurePath(docker.call(['mktemp', '-d'], capture_output=True).strip()) / 'venv'
  
                          docker.call(['python', '-m', 'virtualenv', '--no-download', venv_dir], env=env)
  
                          virtualenv_env = env.copy()
                          virtualenv_env['PATH'] = f"{venv_dir / 'bin'}:{virtualenv_env['PATH']}"
  
                          if options.before_test:
                              before_test_prepared = prepare_command(options.before_test, project=container_project_path, package=container_package_dir)
                              docker.call(['sh', '-c', before_test_prepared], env=virtualenv_env)
  
                          # Install the wheel we just built
                          # Note: If auditwheel produced two wheels, it's because the earlier produced wheel
                          # conforms to multiple manylinux standards. These multiple versions of the wheel are
                          # functionally the same, differing only in name, wheel metadata, and possibly include
                          # different external shared libraries. so it doesn't matter which one we run the tests on.
                          # Let's just pick the first one.
                          wheel_to_test = repaired_wheels[0]
                          docker.call(['pip', 'install', str(wheel_to_test) + options.test_extras], env=virtualenv_env)
  
                          # Install any requirements to run the tests
                          if options.test_requires:
                              docker.call(['pip', 'install', *options.test_requires], env=virtualenv_env)
  
                          # Run the tests from a different directory
                          test_command_prepared = prepare_command(options.test_command, project=container_project_path, package=container_package_dir)
                          docker.call(['sh', '-c', test_command_prepared], cwd='/root', env=virtualenv_env)
  
                          # clean up test environment
                          docker.call(['rm', '-rf', venv_dir])
  
                      # move repaired wheels to output
                      docker.call(['mkdir', '-p', container_output_dir])
                      docker.call(['mv', *repaired_wheels, container_output_dir])
  
                  # copy the output back into the host
                  docker.copy_out(container_output_dir, options.output_dir)
          except subprocess.CalledProcessError as error:
              print(f'Command {error.cmd} failed with code {error.returncode}. {error.stdout}')
              troubleshoot(options.package_dir, error)
              exit(1)
  
  
  def troubleshoot(package_dir: Path, error: Exception) -> None:
      if (isinstance(error, subprocess.CalledProcessError) and error.cmd[0:2] == ['pip', 'wheel']):
          # the 'pip wheel' step failed.
          print('Checking for common errors...')
          so_files = list(package_dir.glob('**/*.so'))
  
          if so_files:
              print(textwrap.dedent('''
                  NOTE: Shared object (.so) files found in this project.
  
                    These files might be built against the wrong OS, causing problems with
                    auditwheel.
  
                    If you're using Cython and have previously done an in-place build,
                    remove those build files (*.so and *.c) before starting cibuildwheel.
              '''), file=sys.stderr)
  
              print('  Files detected:')
              print('\n'.join([f'    {f}' for f in so_files]))
              print('')
  