diff --git a/cibuildwheel/__main__.py b/cibuildwheel/__main__.py index 36b27eab..ce9a72fc 100644 --- a/cibuildwheel/__main__.py +++ b/cibuildwheel/__main__.py @@ -151,9 +151,11 @@ def main(): manylinux_i686_image = os.environ.get('CIBW_MANYLINUX_I686_IMAGE', 'manylinux2010') default_manylinux_images_x86_64 = {'manylinux1': 'quay.io/pypa/manylinux1_x86_64', - 'manylinux2010': 'quay.io/pypa/manylinux2010_x86_64'} + 'manylinux2010': 'quay.io/pypa/manylinux2010_x86_64', + 'manylinux2014': 'quay.io/pypa/manylinux2014_x86_64'} default_manylinux_images_i686 = {'manylinux1': 'quay.io/pypa/manylinux1_i686', - 'manylinux2010': 'quay.io/pypa/manylinux2010_i686'} + 'manylinux2010': 'quay.io/pypa/manylinux2010_i686', + 'manylinux2014': 'quay.io/pypa/manylinux2014_i686'} build_options.update( manylinux_images={'x86_64': default_manylinux_images_x86_64.get(manylinux_x86_64_image) or manylinux_x86_64_image, diff --git a/docs/options.md b/docs/options.md index 80c40d64..08681e86 100644 --- a/docs/options.md +++ b/docs/options.md @@ -240,12 +240,14 @@ CIBW_REPAIR_WHEEL_COMMAND_LINUX: "auditwheel repair --lib-sdir . -w {dest_dir} { An alternative Docker image to be used for building [`manylinux`](https://github.com/pypa/manylinux) wheels. `cibuildwheel` will then pull these instead of the default images, [`quay.io/pypa/manylinux2010_x86_64`](https://quay.io/pypa/manylinux2010_x86_64) and [`quay.io/pypa/manylinux2010_i686`](https://quay.io/pypa/manylinux2010_i686). -The value of this option can either be set to `manylinux1` or `manylinux2010` to use the [official `manylinux` images](https://github.com/pypa/manylinux), or any other valid Docker image name. +The value of this option can either be set to `manylinux1`, `manylinux2010` or `manylinux2014` to use the [official `manylinux` images](https://github.com/pypa/manylinux), or any other valid Docker image name. -Beware to specify a valid Docker image that can be used in the same way as the official, default Docker images: all necessary Python and pip versions need to be present in `/opt/python/`, and the `auditwheel` tool needs to be present for `cibuildwheel` to work. Apart from that, the architecture and relevant shared system libraries need to be manylinux1- or manylinux2010-compatible in order to produce valid `manylinux1`/`manylinux2010` wheels (see https://github.com/pypa/manylinux, [PEP 513](https://www.python.org/dev/peps/pep-0513/), and [PEP 571](https://www.python.org/dev/peps/pep-0571/) for more details). +Beware to specify a valid Docker image that can be used in the same way as the official, default Docker images: all necessary Python and pip versions need to be present in `/opt/python/`, and the `auditwheel` tool needs to be present for `cibuildwheel` to work. Apart from that, the architecture and relevant shared system libraries need to be manylinux1-, manylinux2010- or manylinux2014-compatible in order to produce valid `manylinux1`/`manylinux2010`/`manylinux2014` wheels (see https://github.com/pypa/manylinux, [PEP 513](https://www.python.org/dev/peps/pep-0513/), [PEP 571](https://www.python.org/dev/peps/pep-0571/ and [PEP 599](https://www.python.org/dev/peps/pep-0599/) for more details). Note that `auditwheel` detects the version of the `manylinux` standard in the Docker image through the `AUDITWHEEL_PLAT` environment variable, as `cibuildwheel` has no way of detecting the correct `--plat` command line argument to pass to `auditwheel` for a custom image. If a Docker image does not correctly set this `AUDITWHEEL_PLAT` environment variable, the `CIBW_ENVIRONMENT` option can be used to do so (e.g., `CIBW_ENVIRONMENT="manylinux2010_$(uname -m)"`). +Note that `manylinux2014` doesn't support builds with Python 2.7 - when building with `manylinux2014`, skip Python 2.7 using `CIBW_SKIP` (see example below). + #### Examples ```yaml @@ -253,6 +255,11 @@ Note that `auditwheel` detects the version of the `manylinux` standard in the Do CIBW_MANYLINUX_X86_64_IMAGE: manylinux1 CIBW_MANYLINUX_I686_IMAGE: manylinux1 +# build using the manylinux2014 image +CIBW_MANYLINUX_X86_64_IMAGE: manylinux2014 +CIBW_MANYLINUX_I686_IMAGE: manylinux2014 +CIBW_SKIP: cp27-manylinux* + # build using a different image from the docker registry CIBW_MANYLINUX_X86_64_IMAGE: dockcross/manylinux-x64 CIBW_MANYLINUX_I686_IMAGE: dockcross/manylinux-x86 diff --git a/test/08_manylinux2010_only/cibuildwheel_test.py b/test/08_manylinux2010_only/cibuildwheel_test.py deleted file mode 100644 index e0f0ce56..00000000 --- a/test/08_manylinux2010_only/cibuildwheel_test.py +++ /dev/null @@ -1,20 +0,0 @@ -import os, pytest -import utils - -def test(): - project_dir = os.path.dirname(__file__) - - if utils.platform != 'linux': - pytest.skip('the docker test is only relevant to the linux build') - - # build the wheels - # CFLAGS environment veriable is ecessary to fail on 'malloc_info' (on manylinux1) during compilation/linking, - # rather than when dynamically loading the Python - actual_wheels = utils.cibuildwheel_run(project_dir, add_env={ - 'CIBW_ENVIRONMENT': 'CFLAGS="$CFLAGS -Werror=implicit-function-declaration"', - }) - - # also check that we got the right wheels - expected_wheels = [w for w in utils.expected_wheels('spam', '0.1.0') - if not '-manylinux' in w or '-manylinux2010' in w] - assert set(actual_wheels) == set(expected_wheels) diff --git a/test/08_manylinuxXXXX_only/cibuildwheel_test.py b/test/08_manylinuxXXXX_only/cibuildwheel_test.py new file mode 100644 index 00000000..69c28fcd --- /dev/null +++ b/test/08_manylinuxXXXX_only/cibuildwheel_test.py @@ -0,0 +1,27 @@ +import os, pytest +import utils + + +@pytest.mark.parametrize('manylinux_image', ['manylinux1', 'manylinux2010', 'manylinux2014']) +def test(manylinux_image): + project_dir = os.path.dirname(__file__) + + if utils.platform != 'linux': + pytest.skip('the docker test is only relevant to the linux build') + + # build the wheels + # CFLAGS environment variable is necessary to fail on 'malloc_info' (on manylinux1) during compilation/linking, + # rather than when dynamically loading the Python + add_env = { + 'CIBW_ENVIRONMENT': 'CFLAGS="$CFLAGS -Werror=implicit-function-declaration"', + 'CIBW_MANYLINUX_X86_64_IMAGE': manylinux_image, + 'CIBW_MANYLINUX_I686_IMAGE': manylinux_image, + } + if manylinux_image == 'manylinux2014': + add_env['CIBW_SKIP'] = 'cp27*' # not available on manylinux2014 + actual_wheels = utils.cibuildwheel_run(project_dir, add_env=add_env) + + expected_wheels = [w for w in utils.expected_wheels('spam', '0.1.0', manylinux_versions=[manylinux_image])] + if manylinux_image == 'manylinux2014': + expected_wheels = [w for w in expected_wheels if '-cp27' not in w] + assert set(actual_wheels) == set(expected_wheels) diff --git a/test/08_manylinux2010_only/setup.py b/test/08_manylinuxXXXX_only/setup.py similarity index 100% rename from test/08_manylinux2010_only/setup.py rename to test/08_manylinuxXXXX_only/setup.py diff --git a/test/08_manylinux2010_only/spam.c b/test/08_manylinuxXXXX_only/spam.c similarity index 61% rename from test/08_manylinux2010_only/spam.c rename to test/08_manylinuxXXXX_only/spam.c index 0e50eba9..0e7c5597 100644 --- a/test/08_manylinux2010_only/spam.c +++ b/test/08_manylinuxXXXX_only/spam.c @@ -1,6 +1,12 @@ #include -#if defined(__linux__) #include + +#if !defined(__GLIBC_PREREQ) +#error "Must run on a glibc linux environment" +#endif + +#if !__GLIBC_PREREQ(2, 5) /* manylinux1 is glibc 2.5 */ +#error "Must run on a glibc >= 2.5 linux environment" #endif static PyObject * @@ -12,7 +18,12 @@ spam_system(PyObject *self, PyObject *args) if (!PyArg_ParseTuple(args, "s", &command)) return NULL; -#if defined(__linux__) +#if defined(__GLIBC_PREREQ) && __GLIBC_PREREQ(2, 17) /* manylinux2014 is glibc 2.17 */ + // secure_getenv is only available in manylinux2014, ensuring + // that only a manylinux2014 wheel is produced + sts = (int)secure_getenv("NON_EXISTING_ENV_VARIABLE"); +#elif defined(__GLIBC_PREREQ) && __GLIBC_PREREQ(2, 10) /* manylinux2010 is glibc 2.12 */ + // malloc_info is only available on manylinux2010+ sts = malloc_info(0, stdout); #endif if (sts == 0) { @@ -38,7 +49,7 @@ spam_system(PyObject *self, PyObject *args) #endif static PyMethodDef module_methods[] = { - {"system", (PyCFunction)spam_system, METH_VARARGS, + {"system", (PyCFunction)spam_system, METH_VARARGS, "Execute a shell command."}, {NULL} /* Sentinel */ }; @@ -47,9 +58,9 @@ MOD_INIT(spam) { PyObject* m; - MOD_DEF(m, - "spam", - "Example module", + MOD_DEF(m, + "spam", + "Example module", module_methods, -1) diff --git a/test/shared/utils.py b/test/shared/utils.py index 4291a1d3..4939b64e 100644 --- a/test/shared/utils.py +++ b/test/shared/utils.py @@ -66,67 +66,51 @@ def cibuildwheel_run(project_path, env=None, add_env=None, output_dir=None): return wheels -def expected_wheels(package_name, package_version): +def expected_wheels(package_name, package_version, manylinux_versions=['manylinux1', 'manylinux2010']): ''' Returns a list of expected wheels from a run of cibuildwheel. ''' + # per PEP 425 (https://www.python.org/dev/peps/pep-0425/), wheel files shall have name of the form + # {distribution}-{version}(-{build tag})?-{python tag}-{abi tag}-{platform tag}.whl + # {python tag} and {abi tag} are closely related to the python interpreter used to build the wheel + # so we'll merge them below as python_abi_tag + python_abi_tags = ['cp27-cp27m', 'cp35-cp35m', 'cp36-cp36m', 'cp37-cp37m', 'cp38-cp38'] if platform == 'linux': - templates = [ - '{package_name}-{package_version}-cp27-cp27m-manylinux1_x86_64.whl', - '{package_name}-{package_version}-cp27-cp27mu-manylinux1_x86_64.whl', - '{package_name}-{package_version}-cp35-cp35m-manylinux1_x86_64.whl', - '{package_name}-{package_version}-cp36-cp36m-manylinux1_x86_64.whl', - '{package_name}-{package_version}-cp37-cp37m-manylinux1_x86_64.whl', - '{package_name}-{package_version}-cp38-cp38-manylinux1_x86_64.whl', - '{package_name}-{package_version}-cp27-cp27m-manylinux2010_x86_64.whl', - '{package_name}-{package_version}-cp27-cp27mu-manylinux2010_x86_64.whl', - '{package_name}-{package_version}-cp35-cp35m-manylinux2010_x86_64.whl', - '{package_name}-{package_version}-cp36-cp36m-manylinux2010_x86_64.whl', - '{package_name}-{package_version}-cp37-cp37m-manylinux2010_x86_64.whl', - '{package_name}-{package_version}-cp38-cp38-manylinux2010_x86_64.whl', - '{package_name}-{package_version}-cp27-cp27m-manylinux1_i686.whl', - '{package_name}-{package_version}-cp27-cp27mu-manylinux1_i686.whl', - '{package_name}-{package_version}-cp35-cp35m-manylinux1_i686.whl', - '{package_name}-{package_version}-cp36-cp36m-manylinux1_i686.whl', - '{package_name}-{package_version}-cp37-cp37m-manylinux1_i686.whl', - '{package_name}-{package_version}-cp38-cp38-manylinux1_i686.whl', - '{package_name}-{package_version}-cp27-cp27m-manylinux2010_i686.whl', - '{package_name}-{package_version}-cp27-cp27mu-manylinux2010_i686.whl', - '{package_name}-{package_version}-cp35-cp35m-manylinux2010_i686.whl', - '{package_name}-{package_version}-cp36-cp36m-manylinux2010_i686.whl', - '{package_name}-{package_version}-cp37-cp37m-manylinux2010_i686.whl', - '{package_name}-{package_version}-cp38-cp38-manylinux2010_i686.whl', - ] + python_abi_tags.append('cp27-cp27mu') # python 2.7 has 2 different ABI on manylinux + platform_tags = [] + for architecture in ['x86_64', 'i686']: + for manylinux_version in manylinux_versions: + platform_tags.append('{manylinux_version}_{architecture}'.format( + manylinux_version=manylinux_version, architecture=architecture + )) + def get_platform_tags(python_abi_tag): + return platform_tags elif platform == 'windows': - templates = [ - '{package_name}-{package_version}-cp27-cp27m-win32.whl', - '{package_name}-{package_version}-cp35-cp35m-win32.whl', - '{package_name}-{package_version}-cp36-cp36m-win32.whl', - '{package_name}-{package_version}-cp37-cp37m-win32.whl', - '{package_name}-{package_version}-cp38-cp38-win32.whl', - '{package_name}-{package_version}-cp27-cp27m-win_amd64.whl', - '{package_name}-{package_version}-cp35-cp35m-win_amd64.whl', - '{package_name}-{package_version}-cp36-cp36m-win_amd64.whl', - '{package_name}-{package_version}-cp37-cp37m-win_amd64.whl', - '{package_name}-{package_version}-cp38-cp38-win_amd64.whl', - ] + def get_platform_tags(python_abi_tag): + return ['win32', 'win_amd64'] elif platform == 'macos': - templates = [ - '{package_name}-{package_version}-cp27-cp27m-macosx_10_6_intel.whl', - '{package_name}-{package_version}-cp35-cp35m-macosx_10_6_intel.whl', - '{package_name}-{package_version}-cp36-cp36m-macosx_10_6_intel.whl', - '{package_name}-{package_version}-cp37-cp37m-macosx_10_6_intel.whl', - '{package_name}-{package_version}-cp38-cp38-macosx_10_9_x86_64.whl', - ] + def get_platform_tags(python_abi_tag): + if python_abi_tag == 'cp38-cp38': + return ['macosx_10_9_x86_64'] + else: + return ['macosx_10_6_intel'] else: raise Exception('unsupported platform') + templates = [] + for python_abi_tag in python_abi_tags: + for platform_tag in get_platform_tags(python_abi_tag): + templates.append('{package_name}-{package_version}-{python_abi_tag}-{platform_tag}.whl'.format( + package_name=package_name, package_version=package_version, + python_abi_tag=python_abi_tag, platform_tag=platform_tag + )) + if IS_WINDOWS_RUNNING_ON_TRAVIS: # Python 2.7 isn't supported on Travis. templates = [t for t in templates if '-cp27-' not in t] - return [filename.format(package_name=package_name, package_version=package_version) - for filename in templates] + return templates + platform = None