Merge pull request #256 from joerick/deterministic-builds
Deterministic builds
This commit is contained in:
@@ -13,6 +13,7 @@ jobs:
|
|||||||
- run:
|
- run:
|
||||||
name: Test.
|
name: Test.
|
||||||
command: flake8 .
|
command: flake8 .
|
||||||
|
|
||||||
osx-python3.6:
|
osx-python3.6:
|
||||||
macos:
|
macos:
|
||||||
xcode: "9.4.1"
|
xcode: "9.4.1"
|
||||||
@@ -27,6 +28,7 @@ jobs:
|
|||||||
- run:
|
- run:
|
||||||
name: Test.
|
name: Test.
|
||||||
command: venv/bin/python ./bin/run_tests.py
|
command: venv/bin/python ./bin/run_tests.py
|
||||||
|
no_output_timeout: 30m
|
||||||
|
|
||||||
osx-python3.7:
|
osx-python3.7:
|
||||||
macos:
|
macos:
|
||||||
@@ -42,6 +44,7 @@ jobs:
|
|||||||
- run:
|
- run:
|
||||||
name: Test.
|
name: Test.
|
||||||
command: venv/bin/python ./bin/run_tests.py
|
command: venv/bin/python ./bin/run_tests.py
|
||||||
|
no_output_timeout: 30m
|
||||||
|
|
||||||
linux-python3.6:
|
linux-python3.6:
|
||||||
docker:
|
docker:
|
||||||
@@ -58,6 +61,7 @@ jobs:
|
|||||||
- run:
|
- run:
|
||||||
name: Test.
|
name: Test.
|
||||||
command: venv/bin/python ./bin/run_tests.py
|
command: venv/bin/python ./bin/run_tests.py
|
||||||
|
no_output_timeout: 30m
|
||||||
|
|
||||||
workflows:
|
workflows:
|
||||||
version: 2
|
version: 2
|
||||||
|
|||||||
+3
-1
@@ -89,8 +89,10 @@ venv3/
|
|||||||
venv2/
|
venv2/
|
||||||
ENV/
|
ENV/
|
||||||
env/
|
env/
|
||||||
env3/
|
|
||||||
env2/
|
env2/
|
||||||
|
env2?/
|
||||||
|
env3/
|
||||||
|
env3?/
|
||||||
|
|
||||||
# Spyder project settings
|
# Spyder project settings
|
||||||
.spyderproject
|
.spyderproject
|
||||||
|
|||||||
@@ -22,3 +22,7 @@ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
|
||||||
|
Get-pip and Pip (bundled in cibuildwheel/resources) are licensed under the MIT
|
||||||
|
license. See https://github.com/pypa/get-pip/blob/master/LICENSE.txt
|
||||||
|
|||||||
Executable
+97
@@ -0,0 +1,97 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import configparser
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
os.chdir(os.path.dirname(__file__))
|
||||||
|
os.chdir('..')
|
||||||
|
|
||||||
|
# CUSTOM_COMPILE_COMMAND is a pip-compile option that tells users how to
|
||||||
|
# regenerate the constraints files
|
||||||
|
os.environ['CUSTOM_COMPILE_COMMAND'] = "bin/update_constraints.py"
|
||||||
|
subprocess.check_call([
|
||||||
|
'pip-compile',
|
||||||
|
'--allow-unsafe',
|
||||||
|
'--upgrade',
|
||||||
|
'cibuildwheel/resources/constraints.in',
|
||||||
|
])
|
||||||
|
for python_version in ['27', '35', '36']:
|
||||||
|
subprocess.check_call([
|
||||||
|
f'./env{python_version}/bin/pip-compile',
|
||||||
|
'--allow-unsafe',
|
||||||
|
'--upgrade',
|
||||||
|
'cibuildwheel/resources/constraints.in',
|
||||||
|
'--output-file', f'cibuildwheel/resources/constraints-python{python_version}.txt'
|
||||||
|
])
|
||||||
|
|
||||||
|
Image = namedtuple('Image', [
|
||||||
|
'manylinux_version',
|
||||||
|
'platform',
|
||||||
|
'image_name',
|
||||||
|
])
|
||||||
|
|
||||||
|
images = [
|
||||||
|
Image('manylinux1', 'x86_64', 'quay.io/pypa/manylinux1_x86_64'),
|
||||||
|
Image('manylinux1', 'i686', 'quay.io/pypa/manylinux1_i686'),
|
||||||
|
|
||||||
|
Image('manylinux2010', 'x86_64', 'quay.io/pypa/manylinux2010_x86_64'),
|
||||||
|
Image('manylinux2010', 'i686', 'quay.io/pypa/manylinux2010_i686'),
|
||||||
|
Image('manylinux2010', 'pypy_x86_64', 'pypywheels/manylinux2010-pypy_x86_64'),
|
||||||
|
|
||||||
|
Image('manylinux2014', 'x86_64', 'quay.io/pypa/manylinux2014_x86_64'),
|
||||||
|
Image('manylinux2014', 'i686', 'quay.io/pypa/manylinux2014_i686'),
|
||||||
|
Image('manylinux2014', 'aarch64', 'quay.io/pypa/manylinux2014_aarch64'),
|
||||||
|
Image('manylinux2014', 'ppc64le', 'quay.io/pypa/manylinux2014_ppc64le'),
|
||||||
|
Image('manylinux2014', 's390x', 'quay.io/pypa/manylinux2014_s390x'),
|
||||||
|
]
|
||||||
|
|
||||||
|
config = configparser.ConfigParser()
|
||||||
|
|
||||||
|
for image in images:
|
||||||
|
# get the tag name whose digest matches 'latest'
|
||||||
|
if image.image_name.startswith('quay.io/'):
|
||||||
|
_, _, repository_name = image.image_name.partition('/')
|
||||||
|
response = requests.get(
|
||||||
|
f'https://quay.io/api/v1/repository/{repository_name}?includeTags=true'
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
repo_info = response.json()
|
||||||
|
tags_dict = repo_info['tags']
|
||||||
|
|
||||||
|
latest_tag = tags_dict.pop('latest')
|
||||||
|
# find the tag whose manifest matches 'latest'
|
||||||
|
tag_name = next(
|
||||||
|
name
|
||||||
|
for (name, info) in tags_dict.items()
|
||||||
|
if info['manifest_digest'] == latest_tag['manifest_digest']
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
response = requests.get(
|
||||||
|
f'https://hub.docker.com/v2/repositories/{image.image_name}/tags'
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
tags = response.json()['results']
|
||||||
|
|
||||||
|
latest_tag = next(
|
||||||
|
tag for tag in tags if tag['name'] == 'latest'
|
||||||
|
)
|
||||||
|
# i don't know what it would mean to have multiple images per tag
|
||||||
|
assert len(latest_tag['images']) == 1
|
||||||
|
digest = latest_tag['images'][0]['digest']
|
||||||
|
|
||||||
|
pinned_tag = next(
|
||||||
|
tag for tag in tags if tag['images'][0]['digest'] == digest
|
||||||
|
)
|
||||||
|
tag_name = pinned_tag['name']
|
||||||
|
|
||||||
|
if not config.has_section(image.platform):
|
||||||
|
config[image.platform] = {}
|
||||||
|
|
||||||
|
config[image.platform][image.manylinux_version] = f'{image.image_name}:{tag_name}'
|
||||||
|
|
||||||
|
with open('cibuildwheel/resources/pinned_docker_images.cfg', 'w') as f:
|
||||||
|
config.write(f)
|
||||||
+40
-28
@@ -3,6 +3,7 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
import textwrap
|
import textwrap
|
||||||
import traceback
|
import traceback
|
||||||
|
from configparser import ConfigParser
|
||||||
|
|
||||||
import cibuildwheel
|
import cibuildwheel
|
||||||
import cibuildwheel.linux
|
import cibuildwheel.linux
|
||||||
@@ -14,6 +15,7 @@ from cibuildwheel.environment import (
|
|||||||
)
|
)
|
||||||
from cibuildwheel.util import (
|
from cibuildwheel.util import (
|
||||||
BuildSelector,
|
BuildSelector,
|
||||||
|
DependencyConstraints,
|
||||||
Unbuffered
|
Unbuffered
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -115,6 +117,14 @@ def main():
|
|||||||
environment_config = get_option_from_environment('CIBW_ENVIRONMENT', platform=platform, default='')
|
environment_config = get_option_from_environment('CIBW_ENVIRONMENT', platform=platform, default='')
|
||||||
before_test = get_option_from_environment('CIBW_BEFORE_TEST', platform=platform, default='')
|
before_test = get_option_from_environment('CIBW_BEFORE_TEST', platform=platform, default='')
|
||||||
|
|
||||||
|
dependency_versions = get_option_from_environment('CIBW_DEPENDENCY_VERSIONS', platform=platform, default='pinned')
|
||||||
|
if dependency_versions == 'pinned':
|
||||||
|
dependency_constraints = DependencyConstraints.with_defaults()
|
||||||
|
elif dependency_versions == 'latest':
|
||||||
|
dependency_constraints = None
|
||||||
|
else:
|
||||||
|
dependency_constraints = DependencyConstraints(dependency_versions)
|
||||||
|
|
||||||
if test_extras:
|
if test_extras:
|
||||||
test_extras = '[{0}]'.format(test_extras)
|
test_extras = '[{0}]'.format(test_extras)
|
||||||
|
|
||||||
@@ -155,41 +165,43 @@ def main():
|
|||||||
build_selector=build_selector,
|
build_selector=build_selector,
|
||||||
repair_command=repair_command,
|
repair_command=repair_command,
|
||||||
environment=environment,
|
environment=environment,
|
||||||
before_test=before_test
|
before_test=before_test,
|
||||||
|
dependency_constraints=dependency_constraints,
|
||||||
)
|
)
|
||||||
|
|
||||||
if platform == 'linux':
|
if platform == 'linux':
|
||||||
manylinux_x86_64_image = os.environ.get('CIBW_MANYLINUX_X86_64_IMAGE', 'manylinux2010')
|
pinned_docker_images_file = os.path.join(
|
||||||
manylinux_i686_image = os.environ.get('CIBW_MANYLINUX_I686_IMAGE', 'manylinux2010')
|
os.path.dirname(__file__), 'resources', 'pinned_docker_images.cfg'
|
||||||
manylinux_pypy_x86_64_image = os.environ.get('CIBW_MANYLINUX_PYPY_X86_64_IMAGE', 'manylinux2010')
|
)
|
||||||
manylinux_aarch64_image = os.environ.get('CIBW_MANYLINUX_AARCH64_IMAGE', 'manylinux2014')
|
all_pinned_docker_images = ConfigParser()
|
||||||
manylinux_ppc64le_image = os.environ.get('CIBW_MANYLINUX_PPC64LE_IMAGE', 'manylinux2014')
|
all_pinned_docker_images.read(pinned_docker_images_file)
|
||||||
manylinux_s390x_image = os.environ.get('CIBW_MANYLINUX_S390X_IMAGE', 'manylinux2014')
|
# 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': '...' }
|
||||||
|
# ... }
|
||||||
|
|
||||||
default_manylinux_images_x86_64 = {'manylinux1': 'quay.io/pypa/manylinux1_x86_64',
|
manylinux_images = {}
|
||||||
'manylinux2010': 'quay.io/pypa/manylinux2010_x86_64',
|
|
||||||
'manylinux2014': 'quay.io/pypa/manylinux2014_x86_64'}
|
for build_platform in ['x86_64', 'i686', 'pypy_x86_64', 'aarch64', 'ppc64le', 's390x']:
|
||||||
default_manylinux_images_i686 = {'manylinux1': 'quay.io/pypa/manylinux1_i686',
|
pinned_images = all_pinned_docker_images[build_platform]
|
||||||
'manylinux2010': 'quay.io/pypa/manylinux2010_i686',
|
|
||||||
'manylinux2014': 'quay.io/pypa/manylinux2014_i686'}
|
config_name = 'CIBW_MANYLINUX_{}_IMAGE'.format(build_platform.upper())
|
||||||
default_manylinux_images_pypy_x86_64 = {'manylinux2010': 'pypywheels/manylinux2010-pypy_x86_64'}
|
config_value = os.environ.get(config_name)
|
||||||
default_manylinux_images_aarch64 = {'manylinux2014': 'quay.io/pypa/manylinux2014_aarch64'}
|
|
||||||
default_manylinux_images_ppc64le = {'manylinux2014': 'quay.io/pypa/manylinux2014_ppc64le'}
|
if config_value is None:
|
||||||
default_manylinux_images_s390x = {'manylinux2014': 'quay.io/pypa/manylinux2014_s390x'}
|
# 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
|
||||||
|
|
||||||
|
manylinux_images[build_platform] = image
|
||||||
|
|
||||||
build_options.update(
|
build_options.update(
|
||||||
manylinux_images={'x86_64': default_manylinux_images_x86_64.get(manylinux_x86_64_image) or manylinux_x86_64_image,
|
manylinux_images=manylinux_images
|
||||||
'i686': default_manylinux_images_i686.get(manylinux_i686_image) or manylinux_i686_image,
|
|
||||||
'pypy_x86_64': default_manylinux_images_pypy_x86_64.get(manylinux_pypy_x86_64_image) or manylinux_pypy_x86_64_image,
|
|
||||||
'aarch64': default_manylinux_images_aarch64.get(manylinux_aarch64_image) or manylinux_aarch64_image,
|
|
||||||
'ppc64le': default_manylinux_images_ppc64le.get(manylinux_ppc64le_image) or manylinux_ppc64le_image,
|
|
||||||
's390x': default_manylinux_images_s390x.get(manylinux_s390x_image) or manylinux_s390x_image,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
elif platform == 'macos':
|
|
||||||
pass
|
|
||||||
elif platform == 'windows':
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Python is buffering by default when running on the CI platforms, giving problems interleaving subprocess call output with unflushed calls to 'print'
|
# 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)
|
sys.stdout = Unbuffered(sys.stdout)
|
||||||
|
|||||||
+89
-53
@@ -13,6 +13,13 @@ from .util import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def call(args, input=None, universal_newlines=False):
|
||||||
|
print('+ ' + ' '.join(shlex.quote(a) for a in args))
|
||||||
|
subprocess.run(
|
||||||
|
args, input=input, universal_newlines=universal_newlines, check=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def matches_platform(identifier):
|
def matches_platform(identifier):
|
||||||
pm = platform.machine()
|
pm = platform.machine()
|
||||||
if pm == "x86_64":
|
if pm == "x86_64":
|
||||||
@@ -35,40 +42,40 @@ def matches_platform(identifier):
|
|||||||
|
|
||||||
|
|
||||||
def get_python_configurations(build_selector):
|
def get_python_configurations(build_selector):
|
||||||
PythonConfiguration = namedtuple('PythonConfiguration', ['identifier', 'path'])
|
PythonConfiguration = namedtuple('PythonConfiguration', ['version', 'identifier', 'path'])
|
||||||
python_configurations = [
|
python_configurations = [
|
||||||
PythonConfiguration(identifier='cp27-manylinux_x86_64', path='/opt/python/cp27-cp27m'),
|
PythonConfiguration(version='2.7', identifier='cp27-manylinux_x86_64', path='/opt/python/cp27-cp27m'),
|
||||||
PythonConfiguration(identifier='cp27-manylinux_x86_64', path='/opt/python/cp27-cp27mu'),
|
PythonConfiguration(version='2.7', identifier='cp27-manylinux_x86_64', path='/opt/python/cp27-cp27mu'),
|
||||||
PythonConfiguration(identifier='cp35-manylinux_x86_64', path='/opt/python/cp35-cp35m'),
|
PythonConfiguration(version='3.5', identifier='cp35-manylinux_x86_64', path='/opt/python/cp35-cp35m'),
|
||||||
PythonConfiguration(identifier='cp36-manylinux_x86_64', path='/opt/python/cp36-cp36m'),
|
PythonConfiguration(version='3.6', identifier='cp36-manylinux_x86_64', path='/opt/python/cp36-cp36m'),
|
||||||
PythonConfiguration(identifier='cp37-manylinux_x86_64', path='/opt/python/cp37-cp37m'),
|
PythonConfiguration(version='3.7', identifier='cp37-manylinux_x86_64', path='/opt/python/cp37-cp37m'),
|
||||||
PythonConfiguration(identifier='cp38-manylinux_x86_64', path='/opt/python/cp38-cp38'),
|
PythonConfiguration(version='3.8', identifier='cp38-manylinux_x86_64', path='/opt/python/cp38-cp38'),
|
||||||
PythonConfiguration(identifier='cp27-manylinux_i686', path='/opt/python/cp27-cp27m'),
|
PythonConfiguration(version='2.7', identifier='cp27-manylinux_i686', path='/opt/python/cp27-cp27m'),
|
||||||
PythonConfiguration(identifier='cp27-manylinux_i686', path='/opt/python/cp27-cp27mu'),
|
PythonConfiguration(version='2.7', identifier='cp27-manylinux_i686', path='/opt/python/cp27-cp27mu'),
|
||||||
PythonConfiguration(identifier='cp35-manylinux_i686', path='/opt/python/cp35-cp35m'),
|
PythonConfiguration(version='3.5', identifier='cp35-manylinux_i686', path='/opt/python/cp35-cp35m'),
|
||||||
PythonConfiguration(identifier='cp36-manylinux_i686', path='/opt/python/cp36-cp36m'),
|
PythonConfiguration(version='3.6', identifier='cp36-manylinux_i686', path='/opt/python/cp36-cp36m'),
|
||||||
PythonConfiguration(identifier='cp37-manylinux_i686', path='/opt/python/cp37-cp37m'),
|
PythonConfiguration(version='3.7', identifier='cp37-manylinux_i686', path='/opt/python/cp37-cp37m'),
|
||||||
PythonConfiguration(identifier='cp38-manylinux_i686', path='/opt/python/cp38-cp38'),
|
PythonConfiguration(version='3.8', identifier='cp38-manylinux_i686', path='/opt/python/cp38-cp38'),
|
||||||
PythonConfiguration(identifier='pp27-manylinux_x86_64', path='/opt/python/pp27-pypy_73'),
|
PythonConfiguration(version='2.7', identifier='pp27-manylinux_x86_64', path='/opt/python/pp27-pypy_73'),
|
||||||
PythonConfiguration(identifier='pp36-manylinux_x86_64', path='/opt/python/pp36-pypy36_pp73'),
|
PythonConfiguration(version='3.6', identifier='pp36-manylinux_x86_64', path='/opt/python/pp36-pypy36_pp73'),
|
||||||
PythonConfiguration(identifier='cp35-manylinux_aarch64', path='/opt/python/cp35-cp35m'),
|
PythonConfiguration(version='3.5', identifier='cp35-manylinux_aarch64', path='/opt/python/cp35-cp35m'),
|
||||||
PythonConfiguration(identifier='cp36-manylinux_aarch64', path='/opt/python/cp36-cp36m'),
|
PythonConfiguration(version='3.6', identifier='cp36-manylinux_aarch64', path='/opt/python/cp36-cp36m'),
|
||||||
PythonConfiguration(identifier='cp37-manylinux_aarch64', path='/opt/python/cp37-cp37m'),
|
PythonConfiguration(version='3.7', identifier='cp37-manylinux_aarch64', path='/opt/python/cp37-cp37m'),
|
||||||
PythonConfiguration(identifier='cp38-manylinux_aarch64', path='/opt/python/cp38-cp38'),
|
PythonConfiguration(version='3.8', identifier='cp38-manylinux_aarch64', path='/opt/python/cp38-cp38'),
|
||||||
PythonConfiguration(identifier='cp35-manylinux_ppc64le', path='/opt/python/cp35-cp35m'),
|
PythonConfiguration(version='3.5', identifier='cp35-manylinux_ppc64le', path='/opt/python/cp35-cp35m'),
|
||||||
PythonConfiguration(identifier='cp36-manylinux_ppc64le', path='/opt/python/cp36-cp36m'),
|
PythonConfiguration(version='3.6', identifier='cp36-manylinux_ppc64le', path='/opt/python/cp36-cp36m'),
|
||||||
PythonConfiguration(identifier='cp37-manylinux_ppc64le', path='/opt/python/cp37-cp37m'),
|
PythonConfiguration(version='3.7', identifier='cp37-manylinux_ppc64le', path='/opt/python/cp37-cp37m'),
|
||||||
PythonConfiguration(identifier='cp38-manylinux_ppc64le', path='/opt/python/cp38-cp38'),
|
PythonConfiguration(version='3.8', identifier='cp38-manylinux_ppc64le', path='/opt/python/cp38-cp38'),
|
||||||
PythonConfiguration(identifier='cp35-manylinux_s390x', path='/opt/python/cp35-cp35m'),
|
PythonConfiguration(version='3.5', identifier='cp35-manylinux_s390x', path='/opt/python/cp35-cp35m'),
|
||||||
PythonConfiguration(identifier='cp36-manylinux_s390x', path='/opt/python/cp36-cp36m'),
|
PythonConfiguration(version='3.6', identifier='cp36-manylinux_s390x', path='/opt/python/cp36-cp36m'),
|
||||||
PythonConfiguration(identifier='cp37-manylinux_s390x', path='/opt/python/cp37-cp37m'),
|
PythonConfiguration(version='3.7', identifier='cp37-manylinux_s390x', path='/opt/python/cp37-cp37m'),
|
||||||
PythonConfiguration(identifier='cp38-manylinux_s390x', path='/opt/python/cp38-cp38'),
|
PythonConfiguration(version='3.8', identifier='cp38-manylinux_s390x', path='/opt/python/cp38-cp38'),
|
||||||
]
|
]
|
||||||
# skip builds as required
|
# skip builds as required
|
||||||
return [c for c in python_configurations if matches_platform(c.identifier) and build_selector(c.identifier)]
|
return [c for c in python_configurations if matches_platform(c.identifier) and build_selector(c.identifier)]
|
||||||
|
|
||||||
|
|
||||||
def build(project_dir, output_dir, test_command, before_test, test_requires, test_extras, before_build, build_verbosity, build_selector, repair_command, environment, manylinux_images):
|
def build(project_dir, output_dir, test_command, before_test, test_requires, test_extras, before_build, build_verbosity, build_selector, repair_command, environment, manylinux_images, dependency_constraints):
|
||||||
try:
|
try:
|
||||||
subprocess.check_call(['docker', '--version'])
|
subprocess.check_call(['docker', '--version'])
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -93,14 +100,49 @@ def build(project_dir, output_dir, test_command, before_test, test_requires, tes
|
|||||||
if not platform_configs:
|
if not platform_configs:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
bash_script = '''
|
container_name = 'cibuildwheel-{}'.format(uuid.uuid4())
|
||||||
|
try:
|
||||||
|
call(['docker', 'create',
|
||||||
|
'--env', 'CIBUILDWHEEL',
|
||||||
|
'--name', container_name,
|
||||||
|
'-i',
|
||||||
|
'-v', '/:/host', # ignored on CircleCI
|
||||||
|
docker_image,
|
||||||
|
'/bin/bash'])
|
||||||
|
|
||||||
|
call(['docker', 'cp',
|
||||||
|
os.path.abspath(project_dir) + '/.',
|
||||||
|
container_name + ':/project'])
|
||||||
|
|
||||||
|
call(['docker', 'start', container_name])
|
||||||
|
|
||||||
|
for config in platform_configs:
|
||||||
|
if dependency_constraints:
|
||||||
|
constraints_file = dependency_constraints.get_for_python_version(config.version)
|
||||||
|
|
||||||
|
# `docker cp` causes 'no space left on device' error when
|
||||||
|
# a container is running and the host filesystem is
|
||||||
|
# mounted. https://github.com/moby/moby/issues/38995
|
||||||
|
# Use `docker exec` instead.
|
||||||
|
with open(constraints_file, 'rb') as f:
|
||||||
|
call(
|
||||||
|
['docker', 'exec', '-i', container_name, 'sh', '-c', 'cat > /constraints.txt'],
|
||||||
|
input=f.read(),
|
||||||
|
)
|
||||||
|
|
||||||
|
call(
|
||||||
|
['docker', 'exec', '-i', container_name, '/bin/bash'],
|
||||||
|
universal_newlines=True,
|
||||||
|
input='''
|
||||||
|
# give xtrace output an extra level of indent inside docker
|
||||||
|
PS4=' + '
|
||||||
|
|
||||||
set -o errexit
|
set -o errexit
|
||||||
set -o xtrace
|
set -o xtrace
|
||||||
mkdir /output
|
mkdir -p /output
|
||||||
cd /project
|
cd /project
|
||||||
|
|
||||||
for PYBIN in {pybin_paths}; do (
|
PYBIN="{config_python_bin}"
|
||||||
# Temporary hack/workaround, putting loop body in subshell; fixed in PR #256
|
|
||||||
|
|
||||||
export PATH="$PYBIN:$PATH"
|
export PATH="$PYBIN:$PATH"
|
||||||
{environment_exports}
|
{environment_exports}
|
||||||
@@ -141,10 +183,9 @@ def build(project_dir, output_dir, test_command, before_test, test_requires, tes
|
|||||||
if [ ! -z {test_command} ]; then
|
if [ ! -z {test_command} ]; then
|
||||||
# Set up a virtual environment to install and test from, to make sure
|
# Set up a virtual environment to install and test from, to make sure
|
||||||
# there are no dependencies that were pulled in at build time.
|
# there are no dependencies that were pulled in at build time.
|
||||||
pip install virtualenv
|
pip install {dependency_install_flags} virtualenv
|
||||||
venv_dir=`mktemp -d`/venv
|
venv_dir=`mktemp -d`/venv
|
||||||
python -m virtualenv "$venv_dir"
|
python -m virtualenv --no-download "$venv_dir"
|
||||||
|
|
||||||
export __CIBW_VIRTUALENV_PATH__=$venv_dir
|
export __CIBW_VIRTUALENV_PATH__=$venv_dir
|
||||||
|
|
||||||
# run the tests in a subshell to keep that `activate`
|
# run the tests in a subshell to keep that `activate`
|
||||||
@@ -187,10 +228,11 @@ def build(project_dir, output_dir, test_command, before_test, test_requires, tes
|
|||||||
|
|
||||||
# we're all done here; move it to output
|
# we're all done here; move it to output
|
||||||
mv "${{repaired_wheels[@]}}" /output
|
mv "${{repaired_wheels[@]}}" /output
|
||||||
for repaired_wheel in "${{repaired_wheels[@]}}"; do chown {uid}:{gid} "/output/$(basename "$repaired_wheel")"; done
|
for repaired_wheel in "${{repaired_wheels[@]}}"; do
|
||||||
) done
|
chown {uid}:{gid} "/output/$(basename "$repaired_wheel")"
|
||||||
|
done
|
||||||
'''.format(
|
'''.format(
|
||||||
pybin_paths=' '.join(c.path + '/bin' for c in platform_configs),
|
config_python_bin=config.path + '/bin',
|
||||||
test_requires=' '.join(test_requires),
|
test_requires=' '.join(test_requires),
|
||||||
test_extras=test_extras,
|
test_extras=test_extras,
|
||||||
test_command=shlex.quote(
|
test_command=shlex.quote(
|
||||||
@@ -209,29 +251,23 @@ def build(project_dir, output_dir, test_command, before_test, test_requires, tes
|
|||||||
before_test=shlex.quote(
|
before_test=shlex.quote(
|
||||||
prepare_command(before_test, project='/project') if before_test else ''
|
prepare_command(before_test, project='/project') if before_test else ''
|
||||||
),
|
),
|
||||||
|
dependency_install_flags='-c /constraints.txt' if dependency_constraints else '',
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
container_name = 'cibuildwheel-{}'.format(uuid.uuid4())
|
# copy the output back into the host
|
||||||
try:
|
call(['docker', 'cp',
|
||||||
subprocess.run(['docker', 'create',
|
container_name + ':/output/.',
|
||||||
'--env', 'CIBUILDWHEEL',
|
os.path.abspath(output_dir)])
|
||||||
'--name', container_name,
|
except subprocess.CalledProcessError:
|
||||||
'-i',
|
|
||||||
'-v', '/:/host', # ignored on CircleCI
|
|
||||||
docker_image, '/bin/bash'], check=True)
|
|
||||||
subprocess.run(['docker', 'cp', os.path.abspath(project_dir) + '/.', container_name + ':/project'], check=True)
|
|
||||||
subprocess.run(['docker', 'start', '-i', '-a', container_name], input=bash_script, universal_newlines=True, check=True)
|
|
||||||
subprocess.run(['docker', 'cp', container_name + ':/output/.', os.path.abspath(output_dir)], check=True)
|
|
||||||
except subprocess.CalledProcessError as error:
|
|
||||||
troubleshoot(project_dir, error)
|
|
||||||
exit(1)
|
exit(1)
|
||||||
finally:
|
finally:
|
||||||
# Still gets executed, even when 'exit(1)' gets called
|
# Still gets executed, even when 'exit(1)' gets called
|
||||||
subprocess.run(['docker', 'rm', '--force', '-v', container_name], check=True)
|
call(['docker', 'rm', '--force', '-v', container_name])
|
||||||
|
|
||||||
|
|
||||||
def troubleshoot(project_dir, error):
|
def troubleshoot(project_dir, error):
|
||||||
if (isinstance(error, subprocess.CalledProcessError) and 'start' in error.cmd):
|
if (isinstance(error, subprocess.CalledProcessError) and 'exec' in error.cmd):
|
||||||
# the bash script failed
|
# the bash script failed
|
||||||
print('Checking for common errors...')
|
print('Checking for common errors...')
|
||||||
so_files = []
|
so_files = []
|
||||||
|
|||||||
+20
-14
@@ -11,6 +11,7 @@ from .util import (
|
|||||||
download,
|
download,
|
||||||
get_build_verbosity_extra_flags,
|
get_build_verbosity_extra_flags,
|
||||||
prepare_command,
|
prepare_command,
|
||||||
|
get_pip_script,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -27,13 +28,15 @@ def call(args, env=None, cwd=None, shell=False):
|
|||||||
def get_python_configurations(build_selector):
|
def get_python_configurations(build_selector):
|
||||||
PythonConfiguration = namedtuple('PythonConfiguration', ['version', 'identifier', 'url'])
|
PythonConfiguration = namedtuple('PythonConfiguration', ['version', 'identifier', 'url'])
|
||||||
python_configurations = [
|
python_configurations = [
|
||||||
|
# CPython
|
||||||
PythonConfiguration(version='2.7', identifier='cp27-macosx_x86_64', url='https://www.python.org/ftp/python/2.7.17/python-2.7.17-macosx10.9.pkg'),
|
PythonConfiguration(version='2.7', identifier='cp27-macosx_x86_64', url='https://www.python.org/ftp/python/2.7.17/python-2.7.17-macosx10.9.pkg'),
|
||||||
PythonConfiguration(version='3.5', identifier='cp35-macosx_x86_64', url='https://www.python.org/ftp/python/3.5.4/python-3.5.4-macosx10.6.pkg'),
|
PythonConfiguration(version='3.5', identifier='cp35-macosx_x86_64', url='https://www.python.org/ftp/python/3.5.4/python-3.5.4-macosx10.6.pkg'),
|
||||||
PythonConfiguration(version='3.6', identifier='cp36-macosx_x86_64', url='https://www.python.org/ftp/python/3.6.8/python-3.6.8-macosx10.9.pkg'),
|
PythonConfiguration(version='3.6', identifier='cp36-macosx_x86_64', url='https://www.python.org/ftp/python/3.6.8/python-3.6.8-macosx10.9.pkg'),
|
||||||
PythonConfiguration(version='3.7', identifier='cp37-macosx_x86_64', url='https://www.python.org/ftp/python/3.7.6/python-3.7.6-macosx10.9.pkg'),
|
PythonConfiguration(version='3.7', identifier='cp37-macosx_x86_64', url='https://www.python.org/ftp/python/3.7.6/python-3.7.6-macosx10.9.pkg'),
|
||||||
PythonConfiguration(version='3.8', identifier='cp38-macosx_x86_64', url='https://www.python.org/ftp/python/3.8.2/python-3.8.2-macosx10.9.pkg'),
|
PythonConfiguration(version='3.8', identifier='cp38-macosx_x86_64', url='https://www.python.org/ftp/python/3.8.2/python-3.8.2-macosx10.9.pkg'),
|
||||||
PythonConfiguration(version='2.7-v7.3.0', identifier='pp27-macosx_x86_64', url='https://bitbucket.org/pypy/pypy/downloads/pypy2.7-v7.3.0-osx64.tar.bz2'),
|
# PyPy
|
||||||
PythonConfiguration(version='3.6-v7.3.0', identifier='pp36-macosx_x86_64', url='https://bitbucket.org/pypy/pypy/downloads/pypy3.6-v7.3.0-osx64.tar.bz2'),
|
PythonConfiguration(version='2.7', identifier='pp27-macosx_x86_64', url='https://bitbucket.org/pypy/pypy/downloads/pypy2.7-v7.3.0-osx64.tar.bz2'),
|
||||||
|
PythonConfiguration(version='3.6', identifier='pp36-macosx_x86_64', url='https://bitbucket.org/pypy/pypy/downloads/pypy3.6-v7.3.0-osx64.tar.bz2'),
|
||||||
]
|
]
|
||||||
|
|
||||||
# skip builds as required
|
# skip builds as required
|
||||||
@@ -92,7 +95,7 @@ def install_pypy(version, url):
|
|||||||
call(['tar', '-C', '/tmp', '-xf', os.path.join("/tmp", pypy_tar_bz2)])
|
call(['tar', '-C', '/tmp', '-xf', os.path.join("/tmp", pypy_tar_bz2)])
|
||||||
|
|
||||||
# fix PyPy 7.3.0 bug resulting in wrong macOS platform tag
|
# fix PyPy 7.3.0 bug resulting in wrong macOS platform tag
|
||||||
if version.endswith("-v7.3.0") and version[0] == '3':
|
if "-v7.3.0-" in url and version[0] == '3':
|
||||||
patch_file = os.path.abspath(os.path.join(os.path.dirname(__file__), 'resources', 'pypy3.6.patch'))
|
patch_file = os.path.abspath(os.path.join(os.path.dirname(__file__), 'resources', 'pypy3.6.patch'))
|
||||||
sysconfigdata_file = os.path.join(installation_path, 'lib_pypy', '_sysconfigdata.py')
|
sysconfigdata_file = os.path.join(installation_path, 'lib_pypy', '_sysconfigdata.py')
|
||||||
call(['patch', sysconfigdata_file, patch_file, '-N']) # Always has nonzero return code
|
call(['patch', sysconfigdata_file, patch_file, '-N']) # Always has nonzero return code
|
||||||
@@ -105,7 +108,7 @@ def install_pypy(version, url):
|
|||||||
return installation_bin_path
|
return installation_bin_path
|
||||||
|
|
||||||
|
|
||||||
def build(project_dir, output_dir, test_command, before_test, test_requires, test_extras, before_build, build_verbosity, build_selector, repair_command, environment):
|
def build(project_dir, output_dir, test_command, before_test, test_requires, test_extras, before_build, build_verbosity, build_selector, repair_command, environment, dependency_constraints):
|
||||||
abs_project_dir = os.path.abspath(project_dir)
|
abs_project_dir = os.path.abspath(project_dir)
|
||||||
temp_dir = tempfile.mkdtemp(prefix='cibuildwheel')
|
temp_dir = tempfile.mkdtemp(prefix='cibuildwheel')
|
||||||
built_wheel_dir = os.path.join(temp_dir, 'built_wheel')
|
built_wheel_dir = os.path.join(temp_dir, 'built_wheel')
|
||||||
@@ -113,12 +116,6 @@ def build(project_dir, output_dir, test_command, before_test, test_requires, tes
|
|||||||
|
|
||||||
python_configurations = get_python_configurations(build_selector)
|
python_configurations = get_python_configurations(build_selector)
|
||||||
|
|
||||||
get_pip_url = 'https://bootstrap.pypa.io/get-pip.py'
|
|
||||||
get_pip_script = '/tmp/get-pip.py'
|
|
||||||
|
|
||||||
# get latest pip once and for all
|
|
||||||
download(get_pip_url, get_pip_script)
|
|
||||||
|
|
||||||
for config in python_configurations:
|
for config in python_configurations:
|
||||||
if config.identifier.startswith('cp'):
|
if config.identifier.startswith('cp'):
|
||||||
installation_bin_path = install_cpython(config.version, config.url)
|
installation_bin_path = install_cpython(config.version, config.url)
|
||||||
@@ -151,8 +148,14 @@ def build(project_dir, output_dir, test_command, before_test, test_requires, tes
|
|||||||
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)
|
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)
|
exit(1)
|
||||||
|
|
||||||
|
dependency_constraint_flags = []
|
||||||
|
if dependency_constraints:
|
||||||
|
dependency_constraint_flags = [
|
||||||
|
'-c', dependency_constraints.get_for_python_version(config.version)
|
||||||
|
]
|
||||||
|
|
||||||
# install pip & wheel
|
# install pip & wheel
|
||||||
call(['python', get_pip_script], env=env, cwd="/tmp")
|
call(['python', get_pip_script] + dependency_constraint_flags, env=env, cwd="/tmp")
|
||||||
assert os.path.exists(os.path.join(installation_bin_path, 'pip'))
|
assert os.path.exists(os.path.join(installation_bin_path, 'pip'))
|
||||||
call(['which', 'pip'], env=env)
|
call(['which', 'pip'], env=env)
|
||||||
call(['pip', '--version'], env=env)
|
call(['pip', '--version'], env=env)
|
||||||
@@ -160,7 +163,7 @@ def build(project_dir, output_dir, test_command, before_test, test_requires, tes
|
|||||||
if which_pip != '/tmp/cibw_bin/pip':
|
if which_pip != '/tmp/cibw_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)
|
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)
|
exit(1)
|
||||||
call(['pip', 'install', '--upgrade', 'setuptools', 'wheel', 'delocate'], env=env)
|
call(['pip', 'install', '--upgrade', 'setuptools', 'wheel', 'delocate'] + dependency_constraint_flags, env=env)
|
||||||
|
|
||||||
# setup target platform, only required for python 3.5
|
# setup target platform, only required for python 3.5
|
||||||
if config.version == '3.5':
|
if config.version == '3.5':
|
||||||
@@ -200,9 +203,12 @@ def build(project_dir, output_dir, test_command, before_test, test_requires, tes
|
|||||||
if test_command:
|
if test_command:
|
||||||
# set up a virtual environment to install and test from, to make sure
|
# set up a virtual environment to install and test from, to make sure
|
||||||
# there are no dependencies that were pulled in at build time.
|
# there are no dependencies that were pulled in at build time.
|
||||||
call(['pip', 'install', 'virtualenv'], env=env)
|
call(['pip', 'install', 'virtualenv'] + dependency_constraint_flags, env=env)
|
||||||
venv_dir = tempfile.mkdtemp()
|
venv_dir = tempfile.mkdtemp()
|
||||||
call(['python', '-m', 'virtualenv', venv_dir], env=env)
|
|
||||||
|
# Use --no-download to ensure determinism by using seed libraries
|
||||||
|
# built into virtualenv
|
||||||
|
call(['python', '-m', 'virtualenv', '--no-download', venv_dir], env=env)
|
||||||
|
|
||||||
virtualenv_env = env.copy()
|
virtualenv_env = env.copy()
|
||||||
virtualenv_env['PATH'] = os.pathsep.join([
|
virtualenv_env['PATH'] = os.pathsep.join([
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#
|
||||||
|
# This file is autogenerated by pip-compile
|
||||||
|
# To update, run:
|
||||||
|
#
|
||||||
|
# bin/update_constraints
|
||||||
|
#
|
||||||
|
appdirs==1.4.3 # via virtualenv
|
||||||
|
configparser==4.0.2 # via importlib-metadata
|
||||||
|
contextlib2==0.6.0.post1 # via importlib-metadata, importlib-resources, virtualenv
|
||||||
|
delocate==0.8.0 # via -r cibuildwheel/resources/constraints.in
|
||||||
|
distlib==0.3.0 # via virtualenv
|
||||||
|
filelock==3.0.12 # via virtualenv
|
||||||
|
importlib-metadata==1.5.0 # via importlib-resources, virtualenv
|
||||||
|
importlib-resources==1.3.1 # via virtualenv
|
||||||
|
pathlib2==2.3.5 # via importlib-metadata, importlib-resources, virtualenv
|
||||||
|
scandir==1.10.0 # via pathlib2
|
||||||
|
singledispatch==3.4.0.3 # via importlib-resources
|
||||||
|
six==1.14.0 # via pathlib2, virtualenv
|
||||||
|
typing==3.7.4.1 # via importlib-resources
|
||||||
|
virtualenv==20.0.10 # via -r cibuildwheel/resources/constraints.in
|
||||||
|
wheel==0.34.2 # via -r cibuildwheel/resources/constraints.in, delocate
|
||||||
|
zipp==1.2.0 # via importlib-metadata, importlib-resources
|
||||||
|
|
||||||
|
# The following packages are considered to be unsafe in a requirements file:
|
||||||
|
pip==20.0.2 # via -r cibuildwheel/resources/constraints.in
|
||||||
|
setuptools==44.0.0 # via -r cibuildwheel/resources/constraints.in
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#
|
||||||
|
# This file is autogenerated by pip-compile
|
||||||
|
# To update, run:
|
||||||
|
#
|
||||||
|
# bin/update_constraints
|
||||||
|
#
|
||||||
|
appdirs==1.4.3 # via virtualenv
|
||||||
|
delocate==0.8.0 # via -r cibuildwheel/resources/constraints.in
|
||||||
|
distlib==0.3.0 # via virtualenv
|
||||||
|
filelock==3.0.12 # via virtualenv
|
||||||
|
importlib-metadata==1.5.0 # via importlib-resources, virtualenv
|
||||||
|
importlib-resources==1.3.1 # via virtualenv
|
||||||
|
six==1.14.0 # via virtualenv
|
||||||
|
virtualenv==20.0.10 # via -r cibuildwheel/resources/constraints.in
|
||||||
|
wheel==0.34.2 # via -r cibuildwheel/resources/constraints.in, delocate
|
||||||
|
zipp==1.2.0 # via importlib-metadata, importlib-resources
|
||||||
|
|
||||||
|
# The following packages are considered to be unsafe in a requirements file:
|
||||||
|
pip==20.0.2 # via -r cibuildwheel/resources/constraints.in
|
||||||
|
setuptools==46.0.0 # via -r cibuildwheel/resources/constraints.in
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#
|
||||||
|
# This file is autogenerated by pip-compile
|
||||||
|
# To update, run:
|
||||||
|
#
|
||||||
|
# bin/update_constraints
|
||||||
|
#
|
||||||
|
appdirs==1.4.3 # via virtualenv
|
||||||
|
delocate==0.8.0 # via -r cibuildwheel/resources/constraints.in
|
||||||
|
distlib==0.3.0 # via virtualenv
|
||||||
|
filelock==3.0.12 # via virtualenv
|
||||||
|
importlib-metadata==1.5.0 # via importlib-resources, virtualenv
|
||||||
|
importlib-resources==1.3.1 # via virtualenv
|
||||||
|
six==1.14.0 # via virtualenv
|
||||||
|
virtualenv==20.0.10 # via -r cibuildwheel/resources/constraints.in
|
||||||
|
wheel==0.34.2 # via -r cibuildwheel/resources/constraints.in, delocate
|
||||||
|
zipp==3.1.0 # via importlib-metadata, importlib-resources
|
||||||
|
|
||||||
|
# The following packages are considered to be unsafe in a requirements file:
|
||||||
|
pip==20.0.2 # via -r cibuildwheel/resources/constraints.in
|
||||||
|
setuptools==46.0.0 # via -r cibuildwheel/resources/constraints.in
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
pip
|
||||||
|
setuptools
|
||||||
|
wheel
|
||||||
|
delocate
|
||||||
|
virtualenv
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
#
|
||||||
|
# This file is autogenerated by pip-compile
|
||||||
|
# To update, run:
|
||||||
|
#
|
||||||
|
# bin/update_constraints
|
||||||
|
#
|
||||||
|
appdirs==1.4.3 # via virtualenv
|
||||||
|
delocate==0.8.0 # via -r cibuildwheel/resources/constraints.in
|
||||||
|
distlib==0.3.0 # via virtualenv
|
||||||
|
filelock==3.0.12 # via virtualenv
|
||||||
|
importlib-metadata==1.5.0 # via virtualenv
|
||||||
|
six==1.14.0 # via virtualenv
|
||||||
|
virtualenv==20.0.10 # via -r cibuildwheel/resources/constraints.in
|
||||||
|
wheel==0.34.2 # via -r cibuildwheel/resources/constraints.in, delocate
|
||||||
|
zipp==3.1.0 # via importlib-metadata
|
||||||
|
|
||||||
|
# The following packages are considered to be unsafe in a requirements file:
|
||||||
|
pip==20.0.2 # via -r cibuildwheel/resources/constraints.in
|
||||||
|
setuptools==46.0.0 # via -r cibuildwheel/resources/constraints.in
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,22 @@
|
|||||||
|
[x86_64]
|
||||||
|
manylinux1 = quay.io/pypa/manylinux1_x86_64:2020-03-07-9c5ba95
|
||||||
|
manylinux2010 = quay.io/pypa/manylinux2010_x86_64:2020-03-07-1825e8f
|
||||||
|
manylinux2014 = quay.io/pypa/manylinux2014_x86_64:2020-03-07-06139da
|
||||||
|
|
||||||
|
[i686]
|
||||||
|
manylinux1 = quay.io/pypa/manylinux1_i686:2020-03-07-9c5ba95
|
||||||
|
manylinux2010 = quay.io/pypa/manylinux2010_i686:2020-03-07-1825e8f
|
||||||
|
manylinux2014 = quay.io/pypa/manylinux2014_i686:2020-03-07-06139da
|
||||||
|
|
||||||
|
[pypy_x86_64]
|
||||||
|
manylinux2010 = pypywheels/manylinux2010-pypy_x86_64:latest
|
||||||
|
|
||||||
|
[aarch64]
|
||||||
|
manylinux2014 = quay.io/pypa/manylinux2014_aarch64:2020-03-07-06139da
|
||||||
|
|
||||||
|
[ppc64le]
|
||||||
|
manylinux2014 = quay.io/pypa/manylinux2014_ppc64le:2020-03-07-06139da
|
||||||
|
|
||||||
|
[s390x]
|
||||||
|
manylinux2014 = quay.io/pypa/manylinux2014_s390x:2020-03-07-06139da
|
||||||
|
|
||||||
@@ -76,3 +76,32 @@ def download(url, dest):
|
|||||||
file.write(response.read())
|
file.write(response.read())
|
||||||
finally:
|
finally:
|
||||||
response.close()
|
response.close()
|
||||||
|
|
||||||
|
|
||||||
|
class DependencyConstraints(object):
|
||||||
|
def __init__(self, base_file_path):
|
||||||
|
assert os.path.exists(base_file_path)
|
||||||
|
self.base_file_path = os.path.abspath(base_file_path)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def with_defaults(cls):
|
||||||
|
return cls(
|
||||||
|
base_file_path=os.path.join(os.path.dirname(__file__), 'resources', 'constraints.txt')
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_for_python_version(self, version):
|
||||||
|
version_parts = version.split('.')
|
||||||
|
|
||||||
|
# try to find a version-specific dependency file e.g. if
|
||||||
|
# ./constraints.txt is the base, look for ./constraints-python27.txt
|
||||||
|
base, ext = os.path.splitext(self.base_file_path)
|
||||||
|
specific = base + '-python{}{}'.format(version_parts[0], version_parts[1])
|
||||||
|
specific_file_path = specific + ext
|
||||||
|
if os.path.exists(specific_file_path):
|
||||||
|
return specific_file_path
|
||||||
|
else:
|
||||||
|
return self.base_file_path
|
||||||
|
|
||||||
|
|
||||||
|
resources_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), 'resources'))
|
||||||
|
get_pip_script = os.path.join(resources_dir, 'get-pip.py')
|
||||||
|
|||||||
+20
-11
@@ -11,6 +11,7 @@ from .util import (
|
|||||||
download,
|
download,
|
||||||
get_build_verbosity_extra_flags,
|
get_build_verbosity_extra_flags,
|
||||||
prepare_command,
|
prepare_command,
|
||||||
|
get_pip_script,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -33,6 +34,7 @@ def get_nuget_args(version, arch):
|
|||||||
def get_python_configurations(build_selector):
|
def get_python_configurations(build_selector):
|
||||||
PythonConfiguration = namedtuple('PythonConfiguration', ['version', 'arch', 'identifier', 'url'])
|
PythonConfiguration = namedtuple('PythonConfiguration', ['version', 'arch', 'identifier', 'url'])
|
||||||
python_configurations = [
|
python_configurations = [
|
||||||
|
# CPython
|
||||||
PythonConfiguration(version='2.7.17', arch='32', identifier='cp27-win32', url=None),
|
PythonConfiguration(version='2.7.17', arch='32', identifier='cp27-win32', url=None),
|
||||||
PythonConfiguration(version='2.7.17', arch='64', identifier='cp27-win_amd64', url=None),
|
PythonConfiguration(version='2.7.17', arch='64', identifier='cp27-win_amd64', url=None),
|
||||||
PythonConfiguration(version='3.5.4', arch='32', identifier='cp35-win32', url=None),
|
PythonConfiguration(version='3.5.4', arch='32', identifier='cp35-win32', url=None),
|
||||||
@@ -43,8 +45,9 @@ def get_python_configurations(build_selector):
|
|||||||
PythonConfiguration(version='3.7.6', arch='64', identifier='cp37-win_amd64', url=None),
|
PythonConfiguration(version='3.7.6', arch='64', identifier='cp37-win_amd64', url=None),
|
||||||
PythonConfiguration(version='3.8.2', arch='32', identifier='cp38-win32', url=None),
|
PythonConfiguration(version='3.8.2', arch='32', identifier='cp38-win32', url=None),
|
||||||
PythonConfiguration(version='3.8.2', arch='64', identifier='cp38-win_amd64', url=None),
|
PythonConfiguration(version='3.8.2', arch='64', identifier='cp38-win_amd64', url=None),
|
||||||
PythonConfiguration(version='2.7-v7.3.0', arch='32', identifier='pp27-win32', url='https://bitbucket.org/pypy/pypy/downloads/pypy2.7-v7.3.0-win32.zip'),
|
# PyPy
|
||||||
PythonConfiguration(version='3.6-v7.3.0', arch='32', identifier='pp36-win32', url='https://bitbucket.org/pypy/pypy/downloads/pypy3.6-v7.3.0-win32.zip'),
|
PythonConfiguration(version='2.7', arch='32', identifier='pp27-win32', url='https://bitbucket.org/pypy/pypy/downloads/pypy2.7-v7.3.0-win32.zip'),
|
||||||
|
PythonConfiguration(version='3.6', arch='32', identifier='pp36-win32', url='https://bitbucket.org/pypy/pypy/downloads/pypy3.6-v7.3.0-win32.zip'),
|
||||||
]
|
]
|
||||||
|
|
||||||
if IS_RUNNING_ON_TRAVIS:
|
if IS_RUNNING_ON_TRAVIS:
|
||||||
@@ -86,7 +89,7 @@ def install_pypy(version, arch, url):
|
|||||||
return installation_path
|
return installation_path
|
||||||
|
|
||||||
|
|
||||||
def build(project_dir, output_dir, test_command, before_test, test_requires, test_extras, before_build, build_verbosity, build_selector, repair_command, environment):
|
def build(project_dir, output_dir, test_command, before_test, test_requires, test_extras, before_build, build_verbosity, build_selector, repair_command, environment, dependency_constraints):
|
||||||
abs_project_dir = os.path.abspath(project_dir)
|
abs_project_dir = os.path.abspath(project_dir)
|
||||||
temp_dir = tempfile.mkdtemp(prefix='cibuildwheel')
|
temp_dir = tempfile.mkdtemp(prefix='cibuildwheel')
|
||||||
built_wheel_dir = os.path.join(temp_dir, 'built_wheel')
|
built_wheel_dir = os.path.join(temp_dir, 'built_wheel')
|
||||||
@@ -95,9 +98,6 @@ def build(project_dir, output_dir, test_command, before_test, test_requires, tes
|
|||||||
# install nuget as best way to provide python
|
# install nuget as best way to provide python
|
||||||
nuget = 'C:\\cibw\\nuget.exe'
|
nuget = 'C:\\cibw\\nuget.exe'
|
||||||
download('https://dist.nuget.org/win-x86-commandline/latest/nuget.exe', nuget)
|
download('https://dist.nuget.org/win-x86-commandline/latest/nuget.exe', nuget)
|
||||||
# get pip fo this installation which not have.
|
|
||||||
get_pip_script = 'C:\\cibw\\get-pip.py'
|
|
||||||
download('https://bootstrap.pypa.io/get-pip.py', get_pip_script)
|
|
||||||
|
|
||||||
python_configurations = get_python_configurations(build_selector)
|
python_configurations = get_python_configurations(build_selector)
|
||||||
for config in python_configurations:
|
for config in python_configurations:
|
||||||
@@ -132,9 +132,15 @@ def build(project_dir, output_dir, test_command, before_test, test_requires, tes
|
|||||||
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)
|
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)
|
exit(1)
|
||||||
|
|
||||||
|
dependency_constraint_flags = []
|
||||||
|
if dependency_constraints:
|
||||||
|
dependency_constraint_flags = [
|
||||||
|
'-c', dependency_constraints.get_for_python_version(config.version)
|
||||||
|
]
|
||||||
|
|
||||||
# make sure pip is installed
|
# make sure pip is installed
|
||||||
if not os.path.exists(os.path.join(installation_path, 'Scripts', 'pip.exe')):
|
if not os.path.exists(os.path.join(installation_path, 'Scripts', 'pip.exe')):
|
||||||
shell(['python', get_pip_script], env=env, cwd="C:\\cibw")
|
shell(['python', get_pip_script] + dependency_constraint_flags, env=env, cwd="C:\\cibw")
|
||||||
assert os.path.exists(os.path.join(installation_path, 'Scripts', 'pip.exe'))
|
assert os.path.exists(os.path.join(installation_path, 'Scripts', 'pip.exe'))
|
||||||
where_pip = subprocess.check_output(['where', 'pip'], env=env, universal_newlines=True).splitlines()[0].strip()
|
where_pip = subprocess.check_output(['where', 'pip'], env=env, universal_newlines=True).splitlines()[0].strip()
|
||||||
if where_pip.strip() != os.path.join(installation_path, 'Scripts', 'pip.exe'):
|
if where_pip.strip() != os.path.join(installation_path, 'Scripts', 'pip.exe'):
|
||||||
@@ -142,9 +148,9 @@ def build(project_dir, output_dir, test_command, before_test, test_requires, tes
|
|||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
# prepare the Python environment
|
# prepare the Python environment
|
||||||
shell(['python', '-m', 'pip', 'install', '--upgrade', 'pip'], env=env)
|
shell(['python', '-m', 'pip', 'install', '--upgrade', 'pip'] + dependency_constraint_flags, env=env)
|
||||||
shell(['pip', '--version'], env=env)
|
shell(['pip', '--version'], env=env)
|
||||||
shell(['pip', 'install', '--upgrade', 'setuptools', 'wheel'], env=env)
|
shell(['pip', 'install', '--upgrade', 'setuptools', 'wheel'] + dependency_constraint_flags, env=env)
|
||||||
|
|
||||||
# run the before_build command
|
# run the before_build command
|
||||||
if before_build:
|
if before_build:
|
||||||
@@ -173,9 +179,12 @@ def build(project_dir, output_dir, test_command, before_test, test_requires, tes
|
|||||||
if test_command:
|
if test_command:
|
||||||
# set up a virtual environment to install and test from, to make sure
|
# set up a virtual environment to install and test from, to make sure
|
||||||
# there are no dependencies that were pulled in at build time.
|
# there are no dependencies that were pulled in at build time.
|
||||||
shell(['pip', 'install', 'virtualenv'], env=env)
|
shell(['pip', 'install', 'virtualenv'] + dependency_constraint_flags, env=env)
|
||||||
venv_dir = tempfile.mkdtemp()
|
venv_dir = tempfile.mkdtemp()
|
||||||
shell(['python', '-m', 'virtualenv', venv_dir], env=env)
|
|
||||||
|
# Use --no-download to ensure determinism by using seed libraries
|
||||||
|
# built into virtualenv
|
||||||
|
shell(['python', '-m', 'virtualenv', '--no-download', venv_dir], env=env)
|
||||||
|
|
||||||
virtualenv_env = env.copy()
|
virtualenv_env = env.copy()
|
||||||
|
|
||||||
|
|||||||
+56
-1
@@ -260,7 +260,7 @@ 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), [`quay.io/pypa/manylinux2010_i686`](https://quay.io/pypa/manylinux2010_i686), [`pypywheels/manylinux2010-pypy_x86_64`](https://hub.docker.com/r/pypywheels/manylinux2010-pypy_x86_64), [`quay.io/pypa/manylinux2014_aarch64`](https://quay.io/pypa/manylinux2014_aarch64), [`quay.io/pypa/manylinux2014_ppc64le`](https://quay.io/pypa/manylinux2014_ppc64le), and [`quay.io/pypa/manylinux2014_s390x`](https://quay.io/pypa/manylinux2010_s390x).
|
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), [`quay.io/pypa/manylinux2010_i686`](https://quay.io/pypa/manylinux2010_i686), [`pypywheels/manylinux2010-pypy_x86_64`](https://hub.docker.com/r/pypywheels/manylinux2010-pypy_x86_64), [`quay.io/pypa/manylinux2014_aarch64`](https://quay.io/pypa/manylinux2014_aarch64), [`quay.io/pypa/manylinux2014_ppc64le`](https://quay.io/pypa/manylinux2014_ppc64le), and [`quay.io/pypa/manylinux2014_s390x`](https://quay.io/pypa/manylinux2010_s390x).
|
||||||
|
|
||||||
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) and [PyPy `manylinux` images](https://github.com/pypy/manylinux), or any other valid Docker image name. Note that for PyPy, only the official `manylinux2010` image is currently available. For architectures other
|
The value of this option can either be set to `manylinux1`, `manylinux2010` or `manylinux2014` to use a pinned version of the [official `manylinux` images](https://github.com/pypa/manylinux) and [PyPy `manylinux` images](https://github.com/pypy/manylinux). Alternatively, set these options to any other valid Docker image name. Note that for PyPy, only the official `manylinux2010` image is currently available. For architectures other
|
||||||
than x86 (x86\_64 and i686) manylinux2014 must be used because this is the first version of the manylinux specification that supports additional architectures.
|
than x86 (x86\_64 and i686) manylinux2014 must be used because this is the first version of the manylinux specification that supports additional architectures.
|
||||||
|
|
||||||
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 [pypa/manylinux on GitHub](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).
|
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 [pypa/manylinux on GitHub](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).
|
||||||
@@ -283,11 +283,66 @@ CIBW_MANYLINUX_X86_64_IMAGE: manylinux2014
|
|||||||
CIBW_MANYLINUX_I686_IMAGE: manylinux2014
|
CIBW_MANYLINUX_I686_IMAGE: manylinux2014
|
||||||
CIBW_SKIP: cp27-manylinux*
|
CIBW_SKIP: cp27-manylinux*
|
||||||
|
|
||||||
|
# build using the latest manylinux2010 release, instead of the cibuildwheel
|
||||||
|
# pinned version
|
||||||
|
CIBW_MANYLINUX_X86_64_IMAGE: quay.io/pypa/manylinux2010_x86_64:latest
|
||||||
|
CIBW_MANYLINUX_I686_IMAGE: quay.io/pypa/manylinux2010_i686:latest
|
||||||
|
|
||||||
# build using a different image from the docker registry
|
# build using a different image from the docker registry
|
||||||
CIBW_MANYLINUX_X86_64_IMAGE: dockcross/manylinux-x64
|
CIBW_MANYLINUX_X86_64_IMAGE: dockcross/manylinux-x64
|
||||||
CIBW_MANYLINUX_I686_IMAGE: dockcross/manylinux-x86
|
CIBW_MANYLINUX_I686_IMAGE: dockcross/manylinux-x86
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `CIBW_DEPENDENCY_VERSIONS` {: #dependency-versions}
|
||||||
|
> Specify how cibuildwheel controls the versions of the tools it uses
|
||||||
|
|
||||||
|
Options: `pinned` `latest` `<your constraints file>`
|
||||||
|
|
||||||
|
Default: `pinned`
|
||||||
|
|
||||||
|
If `CIBW_DEPENDENCY_VERSIONS` is `pinned`, cibuildwheel uses versions of tools
|
||||||
|
like `pip`, `setuptools`, `virtualenv` that were pinned with that release of
|
||||||
|
cibuildwheel. This represents a known-good set of dependencies, and is
|
||||||
|
recommended for build repeatability.
|
||||||
|
|
||||||
|
If set to `latest`, cibuildwheel will use the latest of these packages that
|
||||||
|
are available on PyPI. This might be preferable if these packages have bug
|
||||||
|
fixes that can't wait for a new cibuildwheel release.
|
||||||
|
|
||||||
|
To control the versions of dependencies yourself, you can supply a [pip
|
||||||
|
constraints](https://pip.pypa.io/en/stable/user_guide/#constraints-files) file
|
||||||
|
here and it will be used instead.
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
If you need different dependencies for each python version, provide them
|
||||||
|
in the same folder with a `-pythonXY` suffix. e.g. if your
|
||||||
|
`CIBW_DEPENDENCY_VERSIONS=./constraints.txt`, cibuildwheel will use
|
||||||
|
`./constraints-python27.txt` on Python 2.7, or fallback to
|
||||||
|
`./constraints.txt` if that's not found.
|
||||||
|
|
||||||
|
Platform-specific variants also available:<br/>
|
||||||
|
`CIBW_DEPENDENCY_VERSIONS_MACOS` | `CIBW_DEPENDENCY_VERSIONS_WINDOWS`
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
This option does not affect the tools used on the Linux build - those versions
|
||||||
|
are bundled with the manylinux image that cibuildwheel uses. To change
|
||||||
|
dependency versions on Linux, use the [CIBW_MANYLINUX_*](#manylinux-image)
|
||||||
|
options.
|
||||||
|
|
||||||
|
#### Examples
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# use tools versions that are bundled with cibuildwheel (this is the default)
|
||||||
|
CIBW_DEPENDENCY_VERSIONS: pinned
|
||||||
|
|
||||||
|
# use the latest versions available on PyPI
|
||||||
|
CIBW_DEPENDENCY_VERSIONS: latest
|
||||||
|
|
||||||
|
# use your own pip constraints file
|
||||||
|
CIBW_DEPENDENCY_VERSIONS: ./constraints.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
### `CIBW_TEST_COMMAND` {: #test-command}
|
### `CIBW_TEST_COMMAND` {: #test-command}
|
||||||
|
|||||||
@@ -2,3 +2,5 @@
|
|||||||
-e ./docs/mkdocs_include_markdown_plugin
|
-e ./docs/mkdocs_include_markdown_plugin
|
||||||
pytest
|
pytest
|
||||||
mkdocs==1.0.4
|
mkdocs==1.0.4
|
||||||
|
pip-tools
|
||||||
|
requests
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ def test(manylinux_image):
|
|||||||
|
|
||||||
if utils.platform != 'linux':
|
if utils.platform != 'linux':
|
||||||
pytest.skip('the docker test is only relevant to the linux build')
|
pytest.skip('the docker test is only relevant to the linux build')
|
||||||
elif platform.machine not in ['x86_64', 'i686']:
|
elif platform.machine() not in ['x86_64', 'i686']:
|
||||||
if manylinux_image in ['manylinux1', 'manylinux2010']:
|
if manylinux_image in ['manylinux1', 'manylinux2010']:
|
||||||
pytest.skip("manylinux1 and 2010 doesn't exist for non-x86 architectures")
|
pytest.skip("manylinux1 and 2010 doesn't exist for non-x86 architectures")
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,125 @@
|
|||||||
|
import os
|
||||||
|
import re
|
||||||
|
import pytest
|
||||||
|
import textwrap
|
||||||
|
import cibuildwheel.util
|
||||||
|
|
||||||
|
import utils
|
||||||
|
|
||||||
|
VERSION_REGEX = r'([\w-]+)==([^\s]+)'
|
||||||
|
|
||||||
|
|
||||||
|
def get_versions_from_constraint_file(constraint_file):
|
||||||
|
with open(constraint_file, encoding='utf8') as f:
|
||||||
|
constraint_file_text = f.read()
|
||||||
|
|
||||||
|
versions = {}
|
||||||
|
|
||||||
|
for package, version in re.findall(VERSION_REGEX, constraint_file_text):
|
||||||
|
versions[package] = version
|
||||||
|
|
||||||
|
return versions
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('python_version', ['2.7', '3.5', '3.8'])
|
||||||
|
def test_pinned_versions(python_version):
|
||||||
|
if utils.platform == 'linux':
|
||||||
|
pytest.skip('linux doesn\'t pin individual tool versions, it pins manylinux images instead')
|
||||||
|
|
||||||
|
project_dir = os.path.dirname(__file__)
|
||||||
|
|
||||||
|
build_environment = {}
|
||||||
|
|
||||||
|
if python_version == '2.7':
|
||||||
|
constraint_filename = 'constraints-python27.txt'
|
||||||
|
build_pattern = '[cp]p27-*'
|
||||||
|
elif python_version == '3.5':
|
||||||
|
constraint_filename = 'constraints-python35.txt'
|
||||||
|
build_pattern = '[cp]p35-*'
|
||||||
|
else:
|
||||||
|
constraint_filename = 'constraints.txt'
|
||||||
|
build_pattern = '[cp]p38-*'
|
||||||
|
|
||||||
|
constraint_file = os.path.join(cibuildwheel.util.resources_dir, constraint_filename)
|
||||||
|
constraint_versions = get_versions_from_constraint_file(constraint_file)
|
||||||
|
|
||||||
|
for package in ['pip', 'setuptools', 'wheel', 'virtualenv']:
|
||||||
|
env_name = 'EXPECTED_{}_VERSION'.format(package.upper())
|
||||||
|
build_environment[env_name] = constraint_versions[package]
|
||||||
|
|
||||||
|
cibw_environment_option = ' '.join(
|
||||||
|
['{}={}'.format(k, v) for k, v in build_environment.items()]
|
||||||
|
)
|
||||||
|
|
||||||
|
# build and test the wheels
|
||||||
|
actual_wheels = utils.cibuildwheel_run(project_dir, add_env={
|
||||||
|
'CIBW_BUILD': build_pattern,
|
||||||
|
'CIBW_ENVIRONMENT': cibw_environment_option,
|
||||||
|
})
|
||||||
|
|
||||||
|
# also check that we got the right wheels
|
||||||
|
if python_version == '2.7':
|
||||||
|
expected_wheels = [w for w in utils.expected_wheels('spam', '0.1.0')
|
||||||
|
if '-cp27' in w or '-pp27' in w]
|
||||||
|
elif python_version == '3.5':
|
||||||
|
expected_wheels = [w for w in utils.expected_wheels('spam', '0.1.0')
|
||||||
|
if '-cp35' in w or '-pp35' in w]
|
||||||
|
elif python_version == '3.8':
|
||||||
|
expected_wheels = [w for w in utils.expected_wheels('spam', '0.1.0')
|
||||||
|
if '-cp38' in w or '-pp38' in w]
|
||||||
|
else:
|
||||||
|
raise ValueError('unhandled python version')
|
||||||
|
|
||||||
|
assert set(actual_wheels) == set(expected_wheels)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('python_version', ['2.7', '3.x'])
|
||||||
|
def test_dependency_constraints_file(tmp_path, python_version):
|
||||||
|
if utils.platform == 'linux':
|
||||||
|
pytest.skip('linux doesn\'t pin individual tool versions, it pins manylinux images instead')
|
||||||
|
|
||||||
|
project_dir = os.path.dirname(__file__)
|
||||||
|
|
||||||
|
tool_versions = {
|
||||||
|
'pip': '20.0.2',
|
||||||
|
'setuptools': '44.0.0' if python_version == '2.7' else '46.0.0',
|
||||||
|
'wheel': '0.34.2',
|
||||||
|
'virtualenv': '20.0.10',
|
||||||
|
}
|
||||||
|
|
||||||
|
constraints_file = tmp_path / 'constraints.txt'
|
||||||
|
constraints_file.write_text(textwrap.dedent(
|
||||||
|
'''
|
||||||
|
pip=={pip}
|
||||||
|
setuptools=={setuptools}
|
||||||
|
wheel=={wheel}
|
||||||
|
virtualenv=={virtualenv}
|
||||||
|
'''.format(**tool_versions)
|
||||||
|
))
|
||||||
|
|
||||||
|
build_environment = {}
|
||||||
|
|
||||||
|
for package_name, version in tool_versions.items():
|
||||||
|
env_name = 'EXPECTED_{}_VERSION'.format(package_name.upper())
|
||||||
|
build_environment[env_name] = version
|
||||||
|
|
||||||
|
cibw_environment_option = ' '.join(
|
||||||
|
['{}={}'.format(k, v) for k, v in build_environment.items()]
|
||||||
|
)
|
||||||
|
|
||||||
|
# build and test the wheels
|
||||||
|
actual_wheels = utils.cibuildwheel_run(project_dir, add_env={
|
||||||
|
'CIBW_BUILD': '[cp]p27-*' if python_version == '2.7' else '[cp]p3?-*',
|
||||||
|
'CIBW_ENVIRONMENT': cibw_environment_option,
|
||||||
|
'CIBW_DEPENDENCY_VERSIONS': str(constraints_file),
|
||||||
|
})
|
||||||
|
|
||||||
|
# also check that we got the right wheels
|
||||||
|
if python_version == '2.7':
|
||||||
|
expected_wheels = [w for w in utils.expected_wheels('spam', '0.1.0')
|
||||||
|
if '-cp27' in w or '-pp27' in w]
|
||||||
|
else:
|
||||||
|
expected_wheels = [w for w in utils.expected_wheels('spam', '0.1.0')
|
||||||
|
if '-cp27' not in w and '-pp27' not in w]
|
||||||
|
|
||||||
|
assert set(actual_wheels) == set(expected_wheels)
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
from setuptools import (
|
||||||
|
Extension,
|
||||||
|
setup,
|
||||||
|
)
|
||||||
|
|
||||||
|
versions_output_text = subprocess.check_output(
|
||||||
|
['pip', 'freeze', '--all', '-qq'],
|
||||||
|
universal_newlines=True,
|
||||||
|
)
|
||||||
|
versions = versions_output_text.strip().splitlines()
|
||||||
|
|
||||||
|
# `versions` now looks like:
|
||||||
|
# ['pip==x.x.x', 'setuptools==x.x.x', 'wheel==x.x.x']
|
||||||
|
|
||||||
|
print('Gathered versions', versions)
|
||||||
|
|
||||||
|
for package_name in ['pip', 'setuptools', 'wheel']:
|
||||||
|
env_name = 'EXPECTED_{}_VERSION'.format(package_name.upper())
|
||||||
|
expected_version = os.environ[env_name]
|
||||||
|
|
||||||
|
assert '{}=={}'.format(package_name, expected_version) in versions, (
|
||||||
|
'error: {} version should equal {}'.format(package_name, expected_version)
|
||||||
|
)
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name="spam",
|
||||||
|
ext_modules=[Extension('spam', sources=['spam.c'])],
|
||||||
|
version="0.1.0",
|
||||||
|
)
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
#include <Python.h>
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
spam_system(PyObject *self, PyObject *args)
|
||||||
|
{
|
||||||
|
const char *command;
|
||||||
|
int sts;
|
||||||
|
|
||||||
|
if (!PyArg_ParseTuple(args, "s", &command))
|
||||||
|
return NULL;
|
||||||
|
sts = system(command);
|
||||||
|
return PyLong_FromLong(sts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Module initialization */
|
||||||
|
|
||||||
|
#if PY_MAJOR_VERSION >= 3
|
||||||
|
#define MOD_INIT(name) PyMODINIT_FUNC PyInit_##name(void)
|
||||||
|
#define MOD_DEF(m, name, doc, methods, module_state_size) \
|
||||||
|
static struct PyModuleDef moduledef = { \
|
||||||
|
PyModuleDef_HEAD_INIT, name, doc, module_state_size, methods, }; \
|
||||||
|
m = PyModule_Create(&moduledef);
|
||||||
|
#define MOD_RETURN(m) return m;
|
||||||
|
#else
|
||||||
|
#define MOD_INIT(name) PyMODINIT_FUNC init##name(void)
|
||||||
|
#define MOD_DEF(m, name, doc, methods, module_state_size) \
|
||||||
|
m = Py_InitModule3(name, methods, doc);
|
||||||
|
#define MOD_RETURN(m) return;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static PyMethodDef module_methods[] = {
|
||||||
|
{"system", (PyCFunction)spam_system, METH_VARARGS,
|
||||||
|
"Execute a shell command."},
|
||||||
|
{NULL} /* Sentinel */
|
||||||
|
};
|
||||||
|
|
||||||
|
MOD_INIT(spam)
|
||||||
|
{
|
||||||
|
PyObject* m;
|
||||||
|
|
||||||
|
MOD_DEF(m,
|
||||||
|
"spam",
|
||||||
|
"Example module",
|
||||||
|
module_methods,
|
||||||
|
-1)
|
||||||
|
|
||||||
|
MOD_RETURN(m)
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
from cibuildwheel.util import DependencyConstraints
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
def test_defaults():
|
||||||
|
dependency_constraints = DependencyConstraints.with_defaults()
|
||||||
|
|
||||||
|
project_root = os.path.dirname(os.path.dirname(__file__))
|
||||||
|
resources_dir = os.path.join(project_root, 'cibuildwheel', 'resources')
|
||||||
|
|
||||||
|
assert os.path.samefile(
|
||||||
|
dependency_constraints.base_file_path,
|
||||||
|
os.path.join(resources_dir, 'constraints.txt')
|
||||||
|
)
|
||||||
|
assert os.path.samefile(
|
||||||
|
dependency_constraints.get_for_python_version('3.8'),
|
||||||
|
os.path.join(resources_dir, 'constraints.txt')
|
||||||
|
)
|
||||||
|
assert os.path.samefile(
|
||||||
|
dependency_constraints.get_for_python_version('3.6'),
|
||||||
|
os.path.join(resources_dir, 'constraints-python36.txt')
|
||||||
|
)
|
||||||
|
assert os.path.samefile(
|
||||||
|
dependency_constraints.get_for_python_version('3.5'),
|
||||||
|
os.path.join(resources_dir, 'constraints-python35.txt')
|
||||||
|
)
|
||||||
|
assert os.path.samefile(
|
||||||
|
dependency_constraints.get_for_python_version('2.7'),
|
||||||
|
os.path.join(resources_dir, 'constraints-python27.txt')
|
||||||
|
)
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import sys
|
import sys
|
||||||
|
from fnmatch import fnmatch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@@ -57,19 +58,19 @@ def test_build_selector(platform, intercepted_build_args, monkeypatch):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('architecture, image, full_image', [
|
@pytest.mark.parametrize('architecture, image, full_image', [
|
||||||
('x86_64', None, 'quay.io/pypa/manylinux2010_x86_64'),
|
('x86_64', None, 'quay.io/pypa/manylinux2010_x86_64:*'),
|
||||||
('x86_64', 'manylinux1', 'quay.io/pypa/manylinux1_x86_64'),
|
('x86_64', 'manylinux1', 'quay.io/pypa/manylinux1_x86_64:*'),
|
||||||
('x86_64', 'manylinux2010', 'quay.io/pypa/manylinux2010_x86_64'),
|
('x86_64', 'manylinux2010', 'quay.io/pypa/manylinux2010_x86_64:*'),
|
||||||
('x86_64', 'manylinux2014', 'quay.io/pypa/manylinux2014_x86_64'),
|
('x86_64', 'manylinux2014', 'quay.io/pypa/manylinux2014_x86_64:*'),
|
||||||
('x86_64', 'custom_image', 'custom_image'),
|
('x86_64', 'custom_image', 'custom_image'),
|
||||||
('i686', None, 'quay.io/pypa/manylinux2010_i686'),
|
('i686', None, 'quay.io/pypa/manylinux2010_i686:*'),
|
||||||
('i686', 'manylinux1', 'quay.io/pypa/manylinux1_i686'),
|
('i686', 'manylinux1', 'quay.io/pypa/manylinux1_i686:*'),
|
||||||
('i686', 'manylinux2010', 'quay.io/pypa/manylinux2010_i686'),
|
('i686', 'manylinux2010', 'quay.io/pypa/manylinux2010_i686:*'),
|
||||||
('i686', 'manylinux2014', 'quay.io/pypa/manylinux2014_i686'),
|
('i686', 'manylinux2014', 'quay.io/pypa/manylinux2014_i686:*'),
|
||||||
('i686', 'custom_image', 'custom_image'),
|
('i686', 'custom_image', 'custom_image'),
|
||||||
('pypy_x86_64', None, 'pypywheels/manylinux2010-pypy_x86_64'),
|
('pypy_x86_64', None, 'pypywheels/manylinux2010-pypy_x86_64:*'),
|
||||||
('pypy_x86_64', 'manylinux1', 'manylinux1'), # Does not exist
|
('pypy_x86_64', 'manylinux1', 'manylinux1'), # Does not exist
|
||||||
('pypy_x86_64', 'manylinux2010', 'pypywheels/manylinux2010-pypy_x86_64'),
|
('pypy_x86_64', 'manylinux2010', 'pypywheels/manylinux2010-pypy_x86_64:*'),
|
||||||
('pypy_x86_64', 'manylinux2014', 'manylinux2014'), # Does not exist (yet)
|
('pypy_x86_64', 'manylinux2014', 'manylinux2014'), # Does not exist (yet)
|
||||||
('pypy_x86_64', 'custom_image', 'custom_image'),
|
('pypy_x86_64', 'custom_image', 'custom_image'),
|
||||||
])
|
])
|
||||||
@@ -80,7 +81,10 @@ def test_manylinux_images(architecture, image, full_image, platform, intercepted
|
|||||||
main()
|
main()
|
||||||
|
|
||||||
if platform == 'linux':
|
if platform == 'linux':
|
||||||
assert intercepted_build_args.kwargs['manylinux_images'][architecture] == full_image
|
assert fnmatch(
|
||||||
|
intercepted_build_args.kwargs['manylinux_images'][architecture],
|
||||||
|
full_image
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
assert 'manylinux_images' not in intercepted_build_args.kwargs
|
assert 'manylinux_images' not in intercepted_build_args.kwargs
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user