2019-11-12 23:51:27 +00:00
import argparse
import os
import sys
import textwrap
import traceback
2020-02-15 11:45:54 +00:00
from configparser import ConfigParser
2017-03-19 20:57:51 +00:00
2017-04-13 15:02:04 +01:00
import cibuildwheel
2019-11-12 23:51:27 +00:00
import cibuildwheel.linux
import cibuildwheel.macos
import cibuildwheel.windows
from cibuildwheel.environment import (
EnvironmentParseError ,
parse_environment ,
)
2020-01-07 02:42:38 +00:00
from cibuildwheel.util import (
BuildSelector ,
2020-02-15 11:45:54 +00:00
DependencyConstraints ,
2020-01-07 02:42:38 +00:00
Unbuffered
)
2017-03-19 20:57:51 +00:00
2018-07-05 09:58:05 +01:00
def get_option_from_environment ( option_name , platform = None , default = None ):
2017-04-10 21:42:12 +01:00
'''
Returns an option from the environment, optionally scoped by the platform.
Example:
get_option_from_environment('CIBW_COLOR', platform='macos')
This will return the value of CIBW_COLOR_MACOS if it exists, otherwise the value of
CIBW_COLOR.
'''
if platform :
option = os . environ . get ( ' %s _ %s ' % ( option_name , platform . upper ()))
if option is not None :
return option
2018-07-05 09:58:05 +01:00
return os . environ . get ( option_name , default )
2017-04-10 21:42:12 +01:00
2019-11-10 21:27:50 +01:00
def strtobool ( val ):
if val . lower () in ( 'y' , 'yes' , 't' , 'true' , 'on' , '1' ):
return True
return False
2017-03-19 20:57:51 +00:00
def main ():
parser = argparse . ArgumentParser (
description = 'Build wheels for all the platforms.' ,
epilog = ( 'Most options are supplied via environment variables. '
'See https://github.com/joerick/cibuildwheel#options for info.' ))
parser . add_argument ( '--platform' ,
choices = [ 'auto' , 'linux' , 'macos' , 'windows' ],
2017-03-23 11:19:14 +00:00
default = os . environ . get ( 'CIBW_PLATFORM' , 'auto' ),
2017-03-19 20:57:51 +00:00
help = ( 'Platform to build for. For "linux" you need docker running, on Mac '
'or Linux. For "macos", you need a Mac machine, and note that this '
'script is going to automatically install MacPython on your system, '
'so don \' t run on your development machine. For "windows", you need to '
'run in Windows, and it will build and test for all versions of '
2019-11-10 14:33:08 +01:00
'Python. Default: auto.' ))
2017-03-19 20:57:51 +00:00
parser . add_argument ( '--output-dir' ,
default = os . environ . get ( 'CIBW_OUTPUT_DIR' , 'wheelhouse' ),
help = 'Destination folder for the wheels.' )
parser . add_argument ( 'project_dir' ,
default = '.' ,
nargs = '?' ,
help = ( 'Path to the project that you want wheels for. Default: the current '
'directory.' ))
2019-04-20 18:52:01 +02:00
parser . add_argument ( '--print-build-identifiers' ,
action = 'store_true' ,
help = 'Print the build identifiers matched by the current invocation and exit.' )
2017-03-19 20:57:51 +00:00
args = parser . parse_args ()
2019-08-30 18:31:02 +02:00
detect_obsolete_options ()
2019-11-19 23:36:20 +01:00
2017-04-10 21:42:12 +01:00
if args . platform != 'auto' :
platform = args . platform
else :
2019-11-03 16:44:38 +01:00
ci = strtobool ( os . environ . get ( 'CI' , 'false' )) or 'BITRISE_BUILD_NUMBER' in os . environ or 'AZURE_HTTP_USER_AGENT' in os . environ or 'GITHUB_WORKFLOW' in os . environ
2019-11-21 22:45:33 +01:00
if not ci :
2019-11-10 21:27:50 +01:00
print ( 'cibuildwheel: Unable to detect platform. cibuildwheel should run on your CI server, '
2019-11-03 16:44:38 +01:00
'Travis CI, AppVeyor, Azure Pipelines, GitHub Actions and CircleCI are supported. You '
'can run on your development machine or other CI providers using the --platform argument. '
'Check --help output for more information.' ,
2017-04-10 21:42:12 +01:00
file = sys . stderr )
exit ( 2 )
2019-11-21 22:45:33 +01:00
if sys . platform . startswith ( 'linux' ):
platform = 'linux'
elif sys . platform == 'darwin' :
platform = 'macos'
elif sys . platform == 'win32' :
platform = 'windows'
else :
print ( 'cibuildwheel: Unable to detect platform from "sys.platform" in a CI environment. You can run '
'cibuildwheel using the --platform argument. Check --help output for more information.' ,
file = sys . stderr )
exit ( 2 )
2017-03-19 20:57:51 +00:00
output_dir = args . output_dir
2018-07-05 09:58:05 +01:00
test_command = get_option_from_environment ( 'CIBW_TEST_COMMAND' , platform = platform )
test_requires = get_option_from_environment ( 'CIBW_TEST_REQUIRES' , platform = platform , default = '' ) . split ()
2019-09-07 17:17:21 +01:00
test_extras = get_option_from_environment ( 'CIBW_TEST_EXTRAS' , platform = platform , default = '' )
2017-03-19 20:57:51 +00:00
project_dir = args . project_dir
2017-04-10 21:42:12 +01:00
before_build = get_option_from_environment ( 'CIBW_BEFORE_BUILD' , platform = platform )
2018-07-05 09:58:05 +01:00
build_verbosity = get_option_from_environment ( 'CIBW_BUILD_VERBOSITY' , platform = platform , default = '' )
2018-09-16 15:16:30 +02:00
build_config , skip_config = os . environ . get ( 'CIBW_BUILD' , '*' ), os . environ . get ( 'CIBW_SKIP' , '' )
2019-11-12 23:34:59 +00:00
if platform == 'linux' :
repair_command_default = 'auditwheel repair -w {dest_dir} {wheel} '
elif platform == 'macos' :
2019-11-19 23:36:20 +01:00
repair_command_default = 'delocate-listdeps {wheel} && delocate-wheel --require-archs x86_64 -w {dest_dir} {wheel} '
2019-11-12 23:34:59 +00:00
else :
repair_command_default = ''
2019-11-15 02:27:29 +00:00
repair_command = get_option_from_environment ( 'CIBW_REPAIR_WHEEL_COMMAND' , platform = platform , default = repair_command_default )
2018-07-05 09:58:05 +01:00
environment_config = get_option_from_environment ( 'CIBW_ENVIRONMENT' , platform = platform , default = '' )
2020-01-07 00:17:03 +01:00
before_test = get_option_from_environment ( 'CIBW_BEFORE_TEST' , platform = platform , default = '' )
2020-02-02 17:30:27 +00:00
2020-01-28 22:04:37 +00:00
dependency_versions = get_option_from_environment ( 'CIBW_DEPENDENCY_VERSIONS' , platform = platform , default = 'pinned' )
if dependency_versions == 'pinned' :
2020-02-02 17:30:27 +00:00
dependency_constraints = DependencyConstraints . with_defaults ()
2020-01-28 22:04:37 +00:00
elif dependency_versions == 'latest' :
2020-02-02 17:30:27 +00:00
dependency_constraints = None
2020-01-28 22:04:37 +00:00
else :
2020-02-02 17:30:27 +00:00
dependency_constraints = DependencyConstraints ( dependency_versions )
2017-07-24 22:39:37 +01:00
2019-09-07 17:17:21 +01:00
if test_extras :
test_extras = '[ {0} ]' . format ( test_extras )
2018-04-08 21:24:08 +02:00
try :
build_verbosity = min ( 3 , max ( - 3 , int ( build_verbosity )))
except ValueError :
build_verbosity = 0
2017-09-01 13:33:29 +01:00
try :
environment = parse_environment ( environment_config )
2019-11-12 23:51:27 +00:00
except ( EnvironmentParseError , ValueError ):
2017-09-06 19:18:28 +01:00
print ( 'cibuildwheel: Malformed environment option " %s "' % environment_config , file = sys . stderr )
2017-09-01 13:33:29 +01:00
traceback . print_exc ( None , sys . stderr )
exit ( 2 )
2017-04-11 22:57:42 +01:00
2018-09-16 22:49:38 +02:00
build_selector = BuildSelector ( build_config , skip_config )
2017-03-19 20:57:51 +00:00
2017-08-22 21:41:13 +02:00
# Add CIBUILDWHEEL environment variable
# This needs to be passed on to the docker container in linux.py
os . environ [ 'CIBUILDWHEEL' ] = '1'
2018-11-29 23:20:38 +01:00
if not os . path . exists ( os . path . join ( project_dir , 'setup.py' )):
print ( 'cibuildwheel: Could not find setup.py at root of project' , file = sys . stderr )
2018-11-29 02:44:19 +00:00
exit ( 2 )
2019-04-20 18:52:01 +02:00
if args . print_build_identifiers :
print_build_identifiers ( platform , build_selector )
exit ( 0 )
2017-04-13 15:02:04 +01:00
build_options = dict (
2017-03-19 20:57:51 +00:00
project_dir = project_dir ,
output_dir = output_dir ,
test_command = test_command ,
2017-04-10 21:42:12 +01:00
test_requires = test_requires ,
2019-09-07 17:17:21 +01:00
test_extras = test_extras ,
2017-04-10 21:42:12 +01:00
before_build = before_build ,
2018-04-08 21:24:08 +02:00
build_verbosity = build_verbosity ,
2018-09-16 22:49:38 +02:00
build_selector = build_selector ,
2019-11-12 23:34:59 +00:00
repair_command = repair_command ,
2017-07-24 22:39:37 +01:00
environment = environment ,
2020-03-10 21:16:45 +00:00
before_test = before_test ,
2020-01-28 22:04:37 +00:00
dependency_constraints = dependency_constraints ,
2017-03-19 20:57:51 +00:00
)
2017-11-19 23:18:10 +01:00
if platform == 'linux' :
2020-02-02 18:09:29 +00:00
pinned_docker_images_file = os . path . join (
os . path . dirname ( __file__ ), 'resources' , 'pinned_docker_images.cfg'
)
2020-03-07 13:39:57 +00:00
all_pinned_docker_images = ConfigParser ()
all_pinned_docker_images . read ( pinned_docker_images_file )
# all_pinned_docker_images looks like a dict of dicts, e.g.
# { 'x86_64': {'manylinux1': '...', 'manylinux2010': '...', 'manylinux2014': '...'},
# 'i686': {'manylinux1': '...', 'manylinux2010': '...', 'manylinux2014': '...'},
# 'pypy_x86_64': {'manylinux2010': '...' }
# ... }
2020-02-02 18:09:29 +00:00
manylinux_images = {}
2020-03-07 13:39:57 +00:00
for build_platform in [ 'x86_64' , 'i686' , 'pypy_x86_64' , 'aarch64' , 'ppc64le' , 's390x' ]:
pinned_images = all_pinned_docker_images [ build_platform ]
2020-02-02 18:09:29 +00:00
2020-03-07 13:39:57 +00:00
config_name = 'CIBW_MANYLINUX_ {} _IMAGE' . format ( build_platform . upper ())
config_value = os . environ . get ( config_name )
2017-11-19 23:18:10 +01:00
2020-03-07 13:39:57 +00:00
if config_value is None :
# default to manylinux2010 if it's available, otherwise manylinux2014
image = pinned_images . get ( 'manylinux2010' ) or pinned_images . get ( 'manylinux2014' )
elif config_value in pinned_images :
image = pinned_images [ config_value ]
else :
image = config_value
2020-03-01 12:53:07 +00:00
2020-03-07 13:39:57 +00:00
manylinux_images [ build_platform ] = image
2017-11-19 23:18:10 +01:00
build_options . update (
2020-02-02 18:09:29 +00:00
manylinux_images = manylinux_images
2017-11-19 23:18:10 +01:00
)
2018-08-02 12:04:39 +02:00
# Python is buffering by default when running on the CI platforms, giving problems interleaving subprocess call output with unflushed calls to 'print'
sys . stdout = Unbuffered ( sys . stdout )
2017-04-13 15:02:04 +01:00
print_preamble ( platform , build_options )
2017-07-02 18:02:27 -05:00
if not os . path . exists ( output_dir ):
2017-08-31 09:49:57 +02:00
os . makedirs ( output_dir )
2017-07-02 18:02:27 -05:00
2017-03-19 20:57:51 +00:00
if platform == 'linux' :
2017-11-19 23:18:10 +01:00
cibuildwheel . linux . build ( ** build_options )
2017-03-19 20:57:51 +00:00
elif platform == 'windows' :
2017-04-13 15:02:04 +01:00
cibuildwheel . windows . build ( ** build_options )
2017-03-19 20:57:51 +00:00
elif platform == 'macos' :
2017-04-13 15:02:04 +01:00
cibuildwheel . macos . build ( ** build_options )
2017-03-19 20:57:51 +00:00
else :
2019-11-24 12:17:08 +01:00
print ( 'cibuildwheel: Unsupported platform: {} ' . format ( platform ), file = sys . stderr )
exit ( 2 )
2017-03-19 20:57:51 +00:00
2019-04-20 18:52:01 +02:00
2019-08-30 18:31:02 +02:00
def detect_obsolete_options ():
# Check the old 'MANYLINUX1_*_IMAGE' options
for ( deprecated , alternative ) in [( 'CIBW_MANYLINUX1_X86_64_IMAGE' , 'CIBW_MANYLINUX_X86_64_IMAGE' ),
( 'CIBW_MANYLINUX1_I686_IMAGE' , 'CIBW_MANYLINUX_I686_IMAGE' )]:
if deprecated in os . environ :
print ( "' {} ' has been deprecated, and will be removed in a future release. Use the option ' {} ' instead." . format ( deprecated , alternative ))
if alternative not in os . environ :
print ( "Using value of option ' {} ' as replacement for ' {} '" . format ( deprecated , alternative ))
os . environ [ alternative ] = os . environ [ deprecated ]
else :
print ( "Option ' {} ' is not empty. Please unset ' {} '" . format ( alternative , deprecated ))
exit ( 2 )
2020-02-01 21:44:07 +00:00
# Check for deprecated identifiers in 'CIBW_BUILD' and 'CIBW_SKIP' options
for option in [ 'CIBW_BUILD' , 'CIBW_SKIP' ]:
for deprecated , alternative in [( 'manylinux1' , 'manylinux' ),
( 'macosx_10_6_intel' , 'macosx_x86_64' ),
( 'macosx_10_9_x86_64' , 'macosx_x86_64' )]:
if option in os . environ and deprecated in os . environ [ option ]:
print ( "Build identifiers with ' {deprecated} ' have been deprecated. Replacing all occurences of ' {deprecated} ' with ' {alternative} ' in the option ' {option} '" . format (
deprecated = deprecated ,
alternative = alternative ,
option = option ,
))
os . environ [ option ] = os . environ [ option ] . replace ( deprecated , alternative )
2019-08-30 18:31:02 +02:00
2019-11-12 23:51:27 +00:00
2017-04-13 15:02:04 +01:00
def print_preamble ( platform , build_options ):
print ( textwrap . dedent ( '''
_ _ _ _ _ _ _
___|_| |_ _ _|_| |_| |_ _ _| |_ ___ ___| |
| _| | . | | | | | . | | | | | -_| -_| |
|___|_|___|___|_|_|___|_____|_|_|___|___|_|
''' ))
print ( 'cibuildwheel version %s \n ' % cibuildwheel . __version__ )
print ( 'Build options:' )
print ( ' platform: %r ' % platform )
2018-11-30 00:02:18 +01:00
for option , value in sorted ( build_options . items ()):
2017-04-13 15:02:04 +01:00
print ( ' %s : %r ' % ( option , value ))
2018-04-08 18:42:47 +01:00
warnings = detect_warnings ( platform , build_options )
if warnings :
print ( ' \n Warnings:' )
for warning in warnings :
print ( ' ' + warning )
2017-04-13 15:02:04 +01:00
print ( ' \n Here we go! \n ' )
2019-04-20 18:52:01 +02:00
def print_build_identifiers ( platform , build_selector ):
if platform == 'linux' :
python_configurations = cibuildwheel . linux . get_python_configurations ( build_selector )
elif platform == 'windows' :
python_configurations = cibuildwheel . windows . get_python_configurations ( build_selector )
elif platform == 'macos' :
python_configurations = cibuildwheel . macos . get_python_configurations ( build_selector )
else :
python_configurations = []
for config in python_configurations :
print ( config . identifier )
2018-04-08 18:42:47 +01:00
def detect_warnings ( platform , build_options ):
warnings = []
# warn about deprecated {python} and {pip}
for option_name in [ 'test_command' , 'before_build' ]:
2018-04-08 23:38:54 +01:00
option_value = build_options . get ( option_name )
2018-04-08 18:42:47 +01:00
2018-04-08 23:38:54 +01:00
if option_value :
if ' {python} ' in option_value or ' {pip} ' in option_value :
warnings . append ( option_name + ": ' {python} ' and ' {pip} ' are no longer needed, and will be removed in a future release. Simply use 'python' or 'pip' instead." )
2018-04-08 18:42:47 +01:00
return warnings
2017-03-19 20:57:51 +00:00
if __name__ == '__main__' :
main ()