Compare commits

..
151 Commits
Author SHA1 Message Date
Joe Rickerby 783dedad22 Bump version: v1.4.0 2020-05-02 17:55:50 +01:00
Joe Rickerby dd3bec5ba9 Fix #323 2020-05-02 16:57:28 +01:00
Joe Rickerby c9c1ea6fec Update dependencies (#332) 2020-05-02 16:37:38 +01:00
Yannick Jadoul 2e4a29bf1f Updating the description of C++17 support on macOS (#330)
* Updating the description of C++17 support on macOS, adding nuances about runtime library support vs. C++17 compiler/STL features

* Improve phrasing of macOS C++17 docs with suggested edits
2020-05-02 09:58:48 +01:00
Yannick JadoulandJoe Rickerby dadf6a7ac9 PyPy 7.3.1 (#304)
* Testing PyPy 7.3.1rc1/7.3.1rc2

* Try PyPy's fix for macOS

* 7.3.1rc3

* Update PyPy to 7.3.1

* Using latest pypywheels/manylinux2010-pypy_x86_64:2020-04-15-4ce2a6f with PyPy 7.3.1

* Rehost pypy-7.3.1 on Github to guard against bitbucket links going away

The pypy team plan to have a CDN in front of their buildbot downloads
page, but the timeline is unknown. In the meantime, this should be more
reliable than the bitbucket links!

* Updating macOS and Windows download links for PyPy to https://downloads.python.org/pypy/

Co-authored-by: Joe Rickerby <joerick@mac.com>
2020-04-28 19:05:28 +01:00
Matthieu Darbois 9880c4fbb7 Update openssl patch from 1.0.2t to 1.0.2u on macOS (#328) 2020-04-25 16:14:31 +02:00
Matthieu Darbois 2970faf732 Update CPython 3.7 from 3.7.6 to 3.7.7 (#327)
https://docs.python.org/3.7/whatsnew/changelog.html#python-3-7-7-final
2020-04-25 13:07:57 +02:00
Matthieu Darbois a57ef07f72 Update CPython 2.7 from 2.7.17 to 2.7.18 (#326)
change log: https://github.com/python/cpython/blob/v2.7.18/Misc/NEWS.d/2.7.18rc1.rst
2020-04-25 11:16:36 +02:00
Joe Rickerby 0224d72931 Merge pull request #322 from ax3l/doc-fixTravisArm64
Travis-CI Example: Fix aarch64
2020-04-16 07:41:28 +01:00
Axel Huebl 98c4a395e9 Travis-CI Example: Fix aarch64
The `arch:` `aarch64` is called `arm64` on Travis-CI.
https://docs.travis-ci.com/user/multi-cpu-architectures/

Otherwise this falls back to a generic Linux amd64 host.
2020-04-15 18:15:31 -07:00
Joe Rickerby 95678f3d5d Merge pull request #319 from joerick/support-subdirectory-joerick
Provide `package_dir` for setting subdirectory of project [alternative]
2020-04-14 19:12:50 +01:00
Joe Rickerby f7cda33d96 Merge pull request #321 from MaxHalford/master
Update README.md
2020-04-14 19:00:59 +01:00
Joe Rickerby 603f77a0c1 Assert the files were run using capfd 2020-04-12 23:15:49 +01:00
Max Halford a290e00282 Update README.md
Add creme to working examples
2020-04-12 13:13:02 +02:00
Joe Rickerby 7ebe8fef55 Fix tests on windows - shebang don't work so great there! 2020-04-11 09:39:25 +01:00
Joe Rickerby 21e6b99884 Style fixes 2020-04-10 17:44:18 +01:00
Joe Rickerby f4e911028c Add test for building a package at a subpath within a project 2020-04-10 17:42:39 +01:00
Joe Rickerby 3e335044c6 Merge pull request #318 from joerick/appveyor-pty-fix
Try Appveyor config workaround
2020-04-10 17:23:42 +01:00
Joe Rickerby 407a1c0a6e Merge pull request #320 from joerick/reenable-linux-troubleshoot
Add back missing 'troubleshoot' function call
2020-04-10 17:23:21 +01:00
Joe Rickerby fa605d080b Add back missing 'troubleshoot' function call 2020-04-10 14:18:09 +01:00
Joe Rickerby 7b3bcfae34 Remove allow_failures block and add comment 2020-04-10 14:13:42 +01:00
Joe Rickerby 4b5c743a64 And again..? 2020-04-10 13:00:09 +01:00
Joe Rickerby 83b39bcda2 Update docs on the package/project split 2020-04-10 12:59:22 +01:00
Joe Rickerby a1983b94d8 Merge commit '882ea3426a837acfe205cc5de98793c98b6a1bf7' into support-subdirectory-joerick 2020-04-10 12:33:05 +01:00
Joe Rickerby 397393ba91 Use cwd to run test projects, not package_dir 2020-04-10 12:28:05 +01:00
Joe Rickerby e07d8038c6 Remove lingering project_dir references 2020-04-10 12:27:26 +01:00
Joe Rickerby f84388fe26 Changes that let me run the entire the entire linux test suite locally 2020-04-10 12:18:21 +01:00
Joe Rickerby 7e59d8c61c Change how paths are handled, using 'project is working dir' mentality 2020-04-09 16:19:36 +01:00
Joe Rickerby 95bef46832 Update docs for package_dir 2020-04-09 15:51:00 +01:00
Yannick Jadoul 882ea3426a Merge remote-tracking branch 'upstream/master' 2020-04-09 15:20:05 +02:00
Joe Rickerby a0cc4f6347 Fix comma/dot typo 2020-04-09 14:15:56 +01:00
Joe Rickerby d5010b8242 Merge remote-tracking branch 'origin/master' into support-subdirectory 2020-04-09 14:05:09 +01:00
Yannick Jadoul 25f2e8cb46 Fix flake8 2020-04-09 14:37:12 +02:00
Joe Rickerby 7eb0de0dd2 Try another one 2020-04-09 11:39:46 +01:00
Joe Rickerby eff997d12e Merge pull request #311 from Czaki/pack_arguments
Pack build arguments
2020-04-09 10:46:42 +01:00
Joe Rickerby b5175e6159 Try Appveyor config workaround
APPVEYOR_CONSOLE_DISABLE_PTY, as suggested in https://github.com/appveyor/ci/issues/3376#issuecomment-611314299
2020-04-09 10:43:21 +01:00
Yannick Jadoul b8dbedcdbe Remove explicit object base class of DependencyConstraints 2020-04-08 16:43:01 +02:00
Yannick Jadoul 8be6d47eec Merge pull request #313 from YannickJadoul/less-appveyor
Only run unit tests and 01_basic on AppVeyor
2020-04-08 16:24:44 +02:00
Yannick Jadoul b0d99df007 Allow failure for macOS job on AppVeyor 2020-04-08 15:10:48 +02:00
Yannick Jadoul f38eb2644f Run all tests on AppVeyor on master and appveyor-* branches 2020-04-08 13:18:27 +02:00
Grzegorz BokotaandJoe Rickerby ef935187d0 Update BuildOptions description
Co-Authored-By: Joe Rickerby <joerick@mac.com>
2020-04-08 12:54:58 +02:00
Grzegorz Bokota 6113136a1d fix types annotation with optional 2020-04-08 12:54:58 +02:00
Grzegorz Bokota 13a8c549bf rename opt to options 2020-04-08 12:54:58 +02:00
Grzegorz Bokota 8d7d41cd83 python 3.5 fix 2020-04-08 12:54:58 +02:00
Grzegorz Bokota 6554c3cf86 pack build argument in named tuple, introduce typing arguments 2020-04-08 12:54:58 +02:00
Joe Rickerby 3836bb2919 Merge pull request #306 from joerick/min-macosx-deployment-target
Set MACOSX_DEPLOYMENT_TARGET to 10.9, if not set by the user
2020-04-08 11:53:29 +01:00
Joe Rickerby 2345cf20da Merge pull request #314 from joerick/conserve-ci-on-origin-branches
Build PR branches only once (for origin branches) and skip docs changes
2020-04-08 11:40:57 +01:00
Yannick Jadoul 3a3d1a4d2b Only run unit tests and 01_basic on AppVeyor 2020-04-08 10:45:12 +02:00
Yannick Jadoul 49bbbec7f0 Fix flake8 whitespace warnings 2020-04-08 10:24:16 +02:00
Joe Rickerby 2b4bd815ed Fix some whitespace problems with the merge 2020-04-07 18:52:49 +01:00
Joe Rickerby e90e94a18e Merge branch 'master' of github.com:joerick/cibuildwheel into min-macosx-deployment-target 2020-04-07 18:47:22 +01:00
Joe Rickerby bff3e8e37a Merge pull request #307 from Czaki/setup_python_separate
Setup python extracted from build function
2020-04-07 18:24:55 +01:00
Joe Rickerby 1701bc6bde Limit the master-only rule to joerick/cibuildwheel 2020-04-07 18:11:12 +01:00
Joe Rickerby a2eff759e8 Fix github config 2020-04-07 11:54:37 +01:00
Joe Rickerby 1c88697e7c Merge pull request #312 from joerick/refactor-expected-wheels
Refactor expected_wheels test function
2020-04-07 11:50:30 +01:00
Joe Rickerby 97628673d2 Build PR branches only once (for origin branches) and skip docs changes 2020-04-07 11:49:10 +01:00
Grzegorz Bokota 3aa97f09e2 appveyor retries 2020-04-07 12:30:53 +02:00
Joe Rickerby 5a7dc042b6 Add Windows x86 machine values 2020-04-07 10:01:05 +01:00
Grzegorz Bokota a4602c32dd move calculate constraints outside python setup 2020-04-07 00:56:00 +02:00
Joe Rickerby 3018e7a1f8 Refactor expected_wheels function 2020-04-06 23:10:19 +01:00
Joe Rickerby ea6ada0728 Fix update deps script bugs 2020-04-06 14:30:43 +01:00
Joe Rickerby 90634ed872 Fix flake8 exclude list 2020-04-06 11:50:30 +01:00
Joe Rickerby e78a731e2d Fix bug in Docker pin script causing the latest tag to be pinned 2020-04-06 11:47:23 +01:00
Joe Rickerby 8986b2d468 Add dependency update script 2020-04-06 11:45:18 +01:00
Joe Rickerby 626a045770 Merge origin/master into min-macosx-deployment-target 2020-04-05 17:13:53 +01:00
Grzegorz Bokota 8b465bc94a move python setup to separated function 2020-04-05 16:03:35 +02:00
Joe Rickerby 317c0f4baf Merge pull request #256 from joerick/deterministic-builds
Deterministic builds
2020-04-05 12:58:36 +01:00
Joe Rickerby b52edf89ef Tidy up cpp_standards tests, removing 10.9 references as this is default 2020-04-05 12:40:35 +01:00
Joe Rickerby 27b79f8616 Fix tests to always default to 10.9 2020-04-05 12:34:52 +01:00
Joe Rickerby 93df99e43b Remove potentially confusing line in docs 2020-04-05 12:20:07 +01:00
Joe Rickerby 0bdafc5649 Set MACOSX_DEPLOYMENT_TARGET to 10.9, if not set by the user 2020-04-05 12:15:34 +01:00
Joe Rickerby 1eb5fa2311 Merge pull request #302 from henryiii/patch-1
Add GHA example
2020-04-02 22:51:11 +01:00
Henry Schreiner 5367e687f9 Replace most occurrences of nose with pytest 2020-03-31 15:14:27 -04:00
Henry Schreiner abdd1f71d1 Add GHA example 2020-03-31 14:51:32 -04:00
Filip Š ed50cfd20a Fix tests 2020-03-28 16:25:57 +01:00
Joe Rickerby 3fa9935d5b Ensure the correct versions of constraint files are used for each version 2020-03-28 09:52:28 +00:00
Joe Rickerby 13554d0490 Call get-pip.py on Windows with dependency constraint flags 2020-03-24 21:09:07 +00:00
Joe Rickerby 4b6df458fd Docs wording edit 2020-03-23 22:16:47 +00:00
Joe Rickerby de4bf6dafe Add missing version-specific constraint tests 2020-03-23 22:16:47 +00:00
Joe Rickerby ffaad9134b Remove PyPy version from PythonConfiguration 2020-03-18 10:11:09 +00:00
Joe Rickerby 33da3d65c7 Fix CUSTOM_COMPILE_COMMAND and add comment 2020-03-17 20:28:53 +00:00
Joe Rickerby 36a2f00e68 Revert "See stdout during tests to debug circleCI"
This reverts commit dc5dbea4c9.
2020-03-14 18:09:16 +00:00
Joe Rickerby 98dbbaecf4 Fix docker build on CircleCI 2020-03-14 17:27:51 +00:00
Joe Rickerby 7316d69139 Improve logging of docker calls 2020-03-14 17:21:47 +00:00
Joe Rickerby dc5dbea4c9 See stdout during tests to debug circleCI 2020-03-14 17:21:22 +00:00
Joe Rickerby 580b065bb0 Add no_output_timeout to CircleCI tests to prevent them timing out 2020-03-13 17:12:23 +00:00
Joe Rickerby 7090bfa4f1 Stop isn't necessary, docker rm will stop the container 2020-03-13 15:08:37 +00:00
Joe Rickerby 2db9d9a089 The docker container needs a main process 2020-03-13 15:08:10 +00:00
Joe Rickerby 94824f95af Merge remote-tracking branch 'origin/master' into deterministic-builds 2020-03-13 14:20:10 +00:00
Joe Rickerby 4e31c65d09 Workaround docker cp bug
https://github.com/moby/moby/issues/38995
2020-03-13 12:58:23 +00:00
Filip Š bbb77aa488 Update docs 2020-03-13 13:46:51 +01:00
Filip Š 1e08d29cfc Use package_dir to specify path to Python package 2020-03-13 13:45:14 +01:00
Joe Rickerby e49644edf2 Some docker fixes 2020-03-12 09:00:40 +00:00
Joe Rickerby c4f54c6aa3 Run each build using docker exec, for faster builds 2020-03-11 21:55:53 +00:00
Joe Rickerby 7efec9a0fc Try updating more things 2020-03-11 21:50:26 +00:00
Joe Rickerby b4ed192e88 Run dependency tests on pypy too, and update constraint for 3.6 2020-03-11 20:56:11 +00:00
Joe Rickerby b55e98fb2c Update test-pinned version of pip 2020-03-11 11:51:46 +00:00
Joe Rickerby cc956ea6cd Update version of setuptools used to test constraint file 2020-03-11 08:46:01 +00:00
Joe Rickerby 4fb4e44c23 Fix some errors introduced after the merge 2020-03-10 22:33:50 +00:00
Joe Rickerby 8603deac93 Update constraints 2020-03-10 21:50:20 +00:00
Joe Rickerby 7ab2d599b9 Merge branch 'master' of github.com:joerick/cibuildwheel into deterministic-builds
# Conflicts:
#	cibuildwheel/__main__.py
#	cibuildwheel/linux.py
#	cibuildwheel/macos.py
#	cibuildwheel/windows.py
2020-03-10 21:16:45 +00:00
Joe Rickerby 02a52791c9 Fix incorrect constraints file on pypy2.7 2020-03-08 13:12:27 +00:00
Joe Rickerby dd2c27ab01 Some test fixes related to PyPy 2020-03-07 19:35:17 +00:00
Joe Rickerby fd1cd2442c Merge remote-tracking branch 'origin/master' into deterministic-builds 2020-03-07 18:37:57 +00:00
Joe Rickerby ad1b59febf Remove hashes from constraint files 2020-03-07 18:31:39 +00:00
Joe Rickerby 2f1ec582e9 Add constraints for python3.6 too 2020-03-07 17:53:12 +00:00
Joe Rickerby 7c6e69ea28 Add separate constraints for Python3.5
The new virtualenv requires some packages whose latest versions are 3.6+
2020-03-07 17:05:14 +00:00
Joe Rickerby dbc2f06b9a Fix constraint resolution on pypy-2.7 2020-03-07 17:04:28 +00:00
Joe Rickerby 04531d9058 Fix unit tests 2020-03-07 14:53:22 +00:00
Joe Rickerby e06b7696a0 Add missing function call, which causing tests to be skipped
(maybe types are not such a bad idea...)
2020-03-07 14:15:09 +00:00
Joe Rickerby 54fda3f965 Add back the version field to manylinux PythonConfiguration
(needed for constraints)
2020-03-07 14:13:04 +00:00
Joe Rickerby 219266258e Fix missing pypy image name 2020-03-07 14:12:15 +00:00
Joe Rickerby 298df76b8b Consume new config file with pypy and extra architectures 2020-03-07 13:39:57 +00:00
Joe Rickerby 7e583dcab7 Update constraints 2020-03-07 13:24:52 +00:00
Joe Rickerby 9768897764 Add other architectures and pypy to pinned image config.
Also, restructure the file to have platform first, then manylinux version
2020-03-07 13:24:38 +00:00
Joe Rickerby 1880b6d14d Merge remote-tracking branch 'origin/master' into deterministic-builds 2020-03-01 12:53:07 +00:00
Joe Rickerby 4c7453df6c Neater test implementation based on review 2020-03-01 12:32:41 +00:00
Joe Rickerby 5e16e86355 Fix some comments from review 2020-03-01 11:09:22 +00:00
Joe Rickerby 8a450b7655 Fix expected wheel test logic 2020-02-18 22:19:13 +00:00
Joe Rickerby 5187633e26 Add the --no-download flag to virtualenv, to prevent it getting the latest
version of pip, setuptools, wheel from PyPI on creation.

Virtualenv will use bundled version of those tools instead
2020-02-18 22:03:14 +00:00
Joe Rickerby fc20273f8b Remove the unit test check - the versions are determined by virtualenv at creation time 2020-02-18 21:55:03 +00:00
Joe Rickerby f55a5d44a3 Fix typo and add debug logging 2020-02-18 21:03:00 +00:00
Joe Rickerby fd69aed369 Remove dead code 2020-02-18 20:45:13 +00:00
Joe Rickerby ddc4fb2ab8 Add tests for pinned software in setup.py of test project 2020-02-18 20:44:52 +00:00
Joe Rickerby 3c20f70c5d Add test for user-specified constraints file 2020-02-18 20:38:14 +00:00
Joe Rickerby b7fc45afd9 Fix expected wheels in test 2020-02-18 20:21:08 +00:00
Joe Rickerby c9e4a66915 Add a test case for pinned tool versions 2020-02-18 09:33:07 +00:00
Joe Rickerby fe3abfba92 Docs updates 2020-02-18 09:32:02 +00:00
Joe Rickerby bf87df5d62 Ensure subprocess.run commands succeed with check=True 2020-02-15 12:46:55 +00:00
Joe Rickerby 4c076b8d41 Fix piping script into docker bash 2020-02-15 12:32:33 +00:00
Joe Rickerby 50bd6f41da Merge branch 'master' into deterministic-builds 2020-02-15 12:22:34 +00:00
Joe Rickerby f2c2a06c19 Merge fixes and code style 2020-02-15 11:57:31 +00:00
Joe Rickerby 103381774c Merge remote-tracking branch 'origin/master' into deterministic-builds 2020-02-15 11:45:54 +00:00
Joe Rickerby ada46aaf43 Fix exports / version check ordering 2020-02-03 20:50:11 +00:00
Joe Rickerby 17a019b450 Neaten comment 2020-02-03 19:01:17 +00:00
Joe Rickerby bf65679cc3 Fix imports on Python 2 2020-02-03 18:59:32 +00:00
Joe Rickerby a953c5f4ee Remove auditwheel from pinned deps - it's preinstalled on manylinux
auditwheel's pinning of an old version of 'wheel' is causing problems on
mac
2020-02-03 09:02:17 +00:00
Joe Rickerby d8a245dcf6 Use abspath for constraints file 2020-02-02 23:05:58 +00:00
Joe Rickerby e32d0af532 Allow any version of the docker image in the unit test 2020-02-02 22:38:09 +00:00
Joe Rickerby 6177408076 Consume our pinned manylinux image version file for the defaults 2020-02-02 18:09:29 +00:00
Joe Rickerby baa0897f1d Add unit test case and fix filename confusion 2020-02-02 17:46:12 +00:00
Joe Rickerby b9607ffae7 Add ability to pin different dependencies for each python version 2020-02-02 17:30:27 +00:00
Joe Rickerby 076987cb7e Move the config flow control of the linux build into Python
The use of separate bash processes for each build means we can be a bit
freer with environment pollution
2020-02-02 16:03:33 +00:00
Joe Rickerby bad72cbb15 Merge remote-tracking branch 'origin/master' into deterministic-builds 2020-02-02 15:21:42 +00:00
Joe Rickerby 8ead985cfd Generate separate python2 constraint files 2020-01-31 20:27:29 +00:00
Joe Rickerby 1b9b6e9000 Add the ability to pin docker image tags (this isn't consumed yet) 2020-01-31 20:00:30 +00:00
Joe Rickerby 042efd6090 Update constraints 2020-01-31 19:59:57 +00:00
Joe Rickerby b159617a3e Vendor get-pip (version 19.3.1) 2020-01-31 19:08:19 +00:00
Joe Rickerby c0967ad622 Fix docker copy 2020-01-28 22:26:34 +00:00
Joe Rickerby 1cae983592 Add docs for CIBW_DEPENDENCY_VERSIONS 2020-01-28 22:18:37 +00:00
Joe Rickerby 06696c46a0 Pin tool dependencies whenever we pip install using a constraints file 2020-01-28 22:04:37 +00:00
49 changed files with 23744 additions and 520 deletions
+4
View File
@@ -13,6 +13,7 @@ jobs:
- run:
name: Test.
command: flake8 .
osx-python3.6:
macos:
xcode: "9.4.1"
@@ -27,6 +28,7 @@ jobs:
- run:
name: Test.
command: venv/bin/python ./bin/run_tests.py
no_output_timeout: 30m
osx-python3.7:
macos:
@@ -42,6 +44,7 @@ jobs:
- run:
name: Test.
command: venv/bin/python ./bin/run_tests.py
no_output_timeout: 30m
linux-python3.6:
docker:
@@ -58,6 +61,7 @@ jobs:
- run:
name: Test.
command: venv/bin/python ./bin/run_tests.py
no_output_timeout: 30m
workflows:
version: 2
+8 -1
View File
@@ -1,9 +1,16 @@
name: Test
on: [push, pull_request]
on:
push: {}
pull_request:
paths-ignore:
- 'docs/**'
jobs:
test:
# skip branch builds on joerick/cibuildwheel, but always build `master` and pull requests
if: github.repository != 'joerick/cibuildwheel' || github.ref == 'refs/heads/master' || github.event_name == 'pull_request'
name: Test cibuildwheel on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
+3 -1
View File
@@ -89,8 +89,10 @@ venv3/
venv2/
ENV/
env/
env3/
env2/
env2?/
env3/
env3?/
# Spyder project settings
.spyderproject
+4
View File
@@ -1,5 +1,9 @@
language: minimal
branches:
only:
- master
jobs:
include:
- name: Linux | x86_64 + i686 | Python 3.5
+4
View File
@@ -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,
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.
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
+32 -1
View File
@@ -77,7 +77,7 @@ env:
# Note: TWINE_PASSWORD is set to a PyPI API token in Travis settings
install:
- python3 -m pip install cibuildwheel==1.3.0
- python3 -m pip install cibuildwheel==1.4.0
script:
# build the wheels, put them into './wheelhouse'
@@ -127,6 +127,7 @@ Here are some repos that use cibuildwheel.
- [TgCrypto](https://github.com/pyrogram/tgcrypto)
- [Twisted](https://github.com/twisted/twisted)
- [gmic-py](https://github.com/dtschump/gmic-py)
- [creme](https://github.com/creme-ml/creme)
> Add your repo here! Send a PR.
@@ -142,6 +143,36 @@ This is similar to static linking, so it might have some licence implications. C
Changelog
=========
### 1.4.0
_2 May 2020_
- ✨ Deterministic builds. cibuildwheel now locks the versions of the tools it
uses. This means that pinning your version of cibuildwheel pins the versions
of pip, setuptools, manylinux etc. that are used under the hood. This should
make things more reliable. But note that we don't control the entire build
environment on macOS and Windows, where the version of Xcode and Visual
Studio can still effect things.
This can be controlled using the [CIBW_DEPENDENCY_VERSIONS](https://cibuildwheel.readthedocs.io/en/stable/options/#dependency-versions)
and [manylinux image](https://cibuildwheel.readthedocs.io/en/stable/options/#manylinux-image)
options - if you always want to use the latest toolchain, you can still do
that, or you can specify your own pip constraints file and manylinux image.
(#256)
- ✨ Added `package_dir` command line option, meaning we now support building
a package that lives in a subdirectory and pulls in files from the wider
project. See [the `package_dir` option help](https://cibuildwheel.readthedocs.io/en/stable/options/#command-line-options)
for more information.
Note that this change makes the working directory (where you call
cibuildwheel from) relevant on Linux, as it's considered the 'project' and
will be copied into the Docker container. If your builds are slower on this
version, that's likely the reason. `cd` to your project and then call
`cibuildwheel` from there. (#319, #295)
- 🛠 On macOS, we make `MACOSX_DEPLOYMENT_TARGET` default to `10.9` if it's
not set. This should make things more consistent between Python versions.
- 🛠 Dependency updates - CPython 3.7.7, CPython 2.7.18, Pypy 7.3.1.
### 1.3.0
_12 March 2020_
+17 -1
View File
@@ -1,4 +1,7 @@
environment:
# workaround an appveyor bug on macos
# see https://github.com/appveyor/ci/issues/3376#issuecomment-611314299
APPVEYOR_CONSOLE_DISABLE_PTY: true
matrix:
- APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu
APPVEYOR_JOB_NAME: "python37-x64-ubuntu"
@@ -13,9 +16,22 @@ build: off
init:
- cmd: set PATH=C:\Python37;C:\Python37\Scripts;%PATH%
- ps: |
$BRANCH = if ($env:APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH) { $env:APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH } else { $env:APPVEYOR_REPO_BRANCH }
if (-not ($BRANCH -eq 'master' -or $BRANCH.ToLower().StartsWith('appveyor-'))) {
$env:PYTEST_ADDOPTS='-k "unit_test or _basic" --suppress-no-test-exit-code'
}
install: python -m pip install -r requirements-dev.txt
install: python -m pip install -r requirements-dev.txt pytest-custom-exit-code
# the '-u' flag is required so the output is in the correct order.
# See https://github.com/joerick/cibuildwheel/pull/24 for more info.
test_script: python -u ./bin/run_tests.py
branches:
only:
- master
skip_commits:
files:
- docs/*
+75
View File
@@ -0,0 +1,75 @@
#!/usr/bin/env python3
import os
import time
from pathlib import Path
from subprocess import run
import click
import textwrap
def shell(cmd, **kwargs):
return run([cmd], shell=True, **kwargs)
def git_repo_has_changes():
unstaged_changes = shell('git diff-index --quiet HEAD --').returncode != 0
staged_changes = shell('git diff-index --quiet --cached HEAD --').returncode != 0
return unstaged_changes or staged_changes
@click.command()
def main():
project_root = Path(__file__).parent / '..'
os.chdir(project_root)
if git_repo_has_changes():
print('Your git repo has uncommitted changes. Commit or stash before continuing.')
exit(1)
previous_branch = shell('git rev-parse --abbrev-ref HEAD',
check=True,
capture_output=True,
encoding='utf8').stdout.strip()
shell('git fetch origin', check=True)
timestamp = time.strftime('%Y-%m-%dT%H-%M-%S', time.gmtime())
branch_name = f'update-constraints-{timestamp}'
shell(f'git checkout -b {branch_name} origin/master', check=True)
try:
shell('bin/update_dependencies.py', check=True)
if not git_repo_has_changes():
print('Done: no constraint updates required.')
return
shell('git commit -a -m "Update dependencies"', check=True)
run(
[
'gh', 'pr', 'create',
'--repo', 'joerick/cibuildwheel',
'--base', 'master',
'--title', 'Update dependencies',
'--body', textwrap.dedent(f'''
Update the versions of our dependencies.
PR generated by `{os.path.basename(__file__)}`.
''')
],
check=True
)
print('Done.')
finally:
# remove any local changes
shell('git checkout -- .')
shell(f'git checkout {previous_branch}', check=True)
shell(f'git branch -D --force {branch_name}', check=True)
if __name__ == '__main__':
main.main(standalone_mode=True)
+99
View File
@@ -0,0 +1,99 @@
#!/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 != latest_tag and 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)
+1 -1
View File
@@ -1 +1 @@
__version__ = '1.3.0'
__version__ = '1.4.0'
+77 -56
View File
@@ -3,6 +3,7 @@ import os
import sys
import textwrap
import traceback
from configparser import ConfigParser
import cibuildwheel
import cibuildwheel.linux
@@ -14,7 +15,9 @@ from cibuildwheel.environment import (
)
from cibuildwheel.util import (
BuildSelector,
Unbuffered
DependencyConstraints,
Unbuffered,
BuildOptions
)
@@ -45,26 +48,34 @@ def strtobool(val):
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.'))
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'],
default=os.environ.get('CIBW_PLATFORM', 'auto'),
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 '
'Python. Default: auto.'))
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
Python. Default: auto.
''')
parser.add_argument('--output-dir',
default=os.environ.get('CIBW_OUTPUT_DIR', 'wheelhouse'),
help='Destination folder for the wheels.')
parser.add_argument('project_dir',
parser.add_argument('package_dir',
default='.',
nargs='?',
help=('Path to the project that you want wheels for. Default: the current '
'directory.'))
help='''
Path to the package that you want wheels for. Must be a subdirectory of
the working directory. When set, the working directory is still
considered the 'project' and is copied into the Docker container on
Linux. Default: the working directory.
''')
parser.add_argument('--print-build-identifiers',
action='store_true',
@@ -101,7 +112,7 @@ def main():
test_command = get_option_from_environment('CIBW_TEST_COMMAND', platform=platform)
test_requires = get_option_from_environment('CIBW_TEST_REQUIRES', platform=platform, default='').split()
test_extras = get_option_from_environment('CIBW_TEST_EXTRAS', platform=platform, default='')
project_dir = args.project_dir
package_dir = args.package_dir
before_build = get_option_from_environment('CIBW_BEFORE_BUILD', platform=platform)
build_verbosity = get_option_from_environment('CIBW_BUILD_VERBOSITY', platform=platform, default='')
build_config, skip_config = os.environ.get('CIBW_BUILD', '*'), os.environ.get('CIBW_SKIP', '')
@@ -115,6 +126,14 @@ def main():
environment_config = get_option_from_environment('CIBW_ENVIRONMENT', 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:
test_extras = '[{0}]'.format(test_extras)
@@ -136,16 +155,49 @@ def main():
# This needs to be passed on to the docker container in linux.py
os.environ['CIBUILDWHEEL'] = '1'
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)
if not os.path.exists(os.path.join(package_dir, 'setup.py')):
print('cibuildwheel: Could not find setup.py at root of package', file=sys.stderr)
exit(2)
if args.print_build_identifiers:
print_build_identifiers(platform, build_selector)
exit(0)
build_options = dict(
project_dir=project_dir,
if platform == 'linux':
pinned_docker_images_file = os.path.join(
os.path.dirname(__file__), 'resources', 'pinned_docker_images.cfg'
)
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': '...' }
# ... }
manylinux_images = {}
for build_platform in ['x86_64', 'i686', 'pypy_x86_64', 'aarch64', 'ppc64le', 's390x']:
pinned_images = all_pinned_docker_images[build_platform]
config_name = 'CIBW_MANYLINUX_{}_IMAGE'.format(build_platform.upper())
config_value = os.environ.get(config_name)
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
manylinux_images[build_platform] = image
else:
manylinux_images = None
build_options = BuildOptions(
package_dir=package_dir,
output_dir=output_dir,
test_command=test_command,
test_requires=test_requires,
@@ -155,42 +207,11 @@ def main():
build_selector=build_selector,
repair_command=repair_command,
environment=environment,
before_test=before_test
before_test=before_test,
dependency_constraints=dependency_constraints,
manylinux_images=manylinux_images,
)
if platform == 'linux':
manylinux_x86_64_image = os.environ.get('CIBW_MANYLINUX_X86_64_IMAGE', 'manylinux2010')
manylinux_i686_image = os.environ.get('CIBW_MANYLINUX_I686_IMAGE', 'manylinux2010')
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')
manylinux_ppc64le_image = os.environ.get('CIBW_MANYLINUX_PPC64LE_IMAGE', 'manylinux2014')
manylinux_s390x_image = os.environ.get('CIBW_MANYLINUX_S390X_IMAGE', 'manylinux2014')
default_manylinux_images_x86_64 = {'manylinux1': 'quay.io/pypa/manylinux1_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',
'manylinux2014': 'quay.io/pypa/manylinux2014_i686'}
default_manylinux_images_pypy_x86_64 = {'manylinux2010': 'pypywheels/manylinux2010-pypy_x86_64'}
default_manylinux_images_aarch64 = {'manylinux2014': 'quay.io/pypa/manylinux2014_aarch64'}
default_manylinux_images_ppc64le = {'manylinux2014': 'quay.io/pypa/manylinux2014_ppc64le'}
default_manylinux_images_s390x = {'manylinux2014': 'quay.io/pypa/manylinux2014_s390x'}
build_options.update(
manylinux_images={'x86_64': default_manylinux_images_x86_64.get(manylinux_x86_64_image) or manylinux_x86_64_image,
'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'
sys.stdout = Unbuffered(sys.stdout)
@@ -200,11 +221,11 @@ def main():
os.makedirs(output_dir)
if platform == 'linux':
cibuildwheel.linux.build(**build_options)
cibuildwheel.linux.build(build_options)
elif platform == 'windows':
cibuildwheel.windows.build(**build_options)
cibuildwheel.windows.build(build_options)
elif platform == 'macos':
cibuildwheel.macos.build(**build_options)
cibuildwheel.macos.build(build_options)
else:
print('cibuildwheel: Unsupported platform: {}'.format(platform), file=sys.stderr)
exit(2)
@@ -249,7 +270,7 @@ def print_preamble(platform, build_options):
print('Build options:')
print(' platform: %r' % platform)
for option, value in sorted(build_options.items()):
for option, value in sorted(build_options._asdict().items()):
print(' %s: %r' % (option, value))
warnings = detect_warnings(platform, build_options)
@@ -280,7 +301,7 @@ def detect_warnings(platform, build_options):
# warn about deprecated {python} and {pip}
for option_name in ['test_command', 'before_build']:
option_value = build_options.get(option_name)
option_value = getattr(build_options, option_name)
if option_value:
if '{python}' in option_value or '{pip}' in option_value:
+210 -167
View File
@@ -10,9 +10,17 @@ from collections import namedtuple
from .util import (
get_build_verbosity_extra_flags,
prepare_command,
BuildOptions
)
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):
pm = platform.machine()
if pm == "x86_64":
@@ -35,40 +43,40 @@ def matches_platform(identifier):
def get_python_configurations(build_selector):
PythonConfiguration = namedtuple('PythonConfiguration', ['identifier', 'path'])
PythonConfiguration = namedtuple('PythonConfiguration', ['version', 'identifier', 'path'])
python_configurations = [
PythonConfiguration(identifier='cp27-manylinux_x86_64', path='/opt/python/cp27-cp27m'),
PythonConfiguration(identifier='cp27-manylinux_x86_64', path='/opt/python/cp27-cp27mu'),
PythonConfiguration(identifier='cp35-manylinux_x86_64', path='/opt/python/cp35-cp35m'),
PythonConfiguration(identifier='cp36-manylinux_x86_64', path='/opt/python/cp36-cp36m'),
PythonConfiguration(identifier='cp37-manylinux_x86_64', path='/opt/python/cp37-cp37m'),
PythonConfiguration(identifier='cp38-manylinux_x86_64', path='/opt/python/cp38-cp38'),
PythonConfiguration(identifier='cp27-manylinux_i686', path='/opt/python/cp27-cp27m'),
PythonConfiguration(identifier='cp27-manylinux_i686', path='/opt/python/cp27-cp27mu'),
PythonConfiguration(identifier='cp35-manylinux_i686', path='/opt/python/cp35-cp35m'),
PythonConfiguration(identifier='cp36-manylinux_i686', path='/opt/python/cp36-cp36m'),
PythonConfiguration(identifier='cp37-manylinux_i686', path='/opt/python/cp37-cp37m'),
PythonConfiguration(identifier='cp38-manylinux_i686', path='/opt/python/cp38-cp38'),
PythonConfiguration(identifier='pp27-manylinux_x86_64', path='/opt/python/pp27-pypy_73'),
PythonConfiguration(identifier='pp36-manylinux_x86_64', path='/opt/python/pp36-pypy36_pp73'),
PythonConfiguration(identifier='cp35-manylinux_aarch64', path='/opt/python/cp35-cp35m'),
PythonConfiguration(identifier='cp36-manylinux_aarch64', path='/opt/python/cp36-cp36m'),
PythonConfiguration(identifier='cp37-manylinux_aarch64', path='/opt/python/cp37-cp37m'),
PythonConfiguration(identifier='cp38-manylinux_aarch64', path='/opt/python/cp38-cp38'),
PythonConfiguration(identifier='cp35-manylinux_ppc64le', path='/opt/python/cp35-cp35m'),
PythonConfiguration(identifier='cp36-manylinux_ppc64le', path='/opt/python/cp36-cp36m'),
PythonConfiguration(identifier='cp37-manylinux_ppc64le', path='/opt/python/cp37-cp37m'),
PythonConfiguration(identifier='cp38-manylinux_ppc64le', path='/opt/python/cp38-cp38'),
PythonConfiguration(identifier='cp35-manylinux_s390x', path='/opt/python/cp35-cp35m'),
PythonConfiguration(identifier='cp36-manylinux_s390x', path='/opt/python/cp36-cp36m'),
PythonConfiguration(identifier='cp37-manylinux_s390x', path='/opt/python/cp37-cp37m'),
PythonConfiguration(identifier='cp38-manylinux_s390x', path='/opt/python/cp38-cp38'),
PythonConfiguration(version='2.7', identifier='cp27-manylinux_x86_64', path='/opt/python/cp27-cp27m'),
PythonConfiguration(version='2.7', identifier='cp27-manylinux_x86_64', path='/opt/python/cp27-cp27mu'),
PythonConfiguration(version='3.5', identifier='cp35-manylinux_x86_64', path='/opt/python/cp35-cp35m'),
PythonConfiguration(version='3.6', identifier='cp36-manylinux_x86_64', path='/opt/python/cp36-cp36m'),
PythonConfiguration(version='3.7', identifier='cp37-manylinux_x86_64', path='/opt/python/cp37-cp37m'),
PythonConfiguration(version='3.8', identifier='cp38-manylinux_x86_64', path='/opt/python/cp38-cp38'),
PythonConfiguration(version='2.7', identifier='cp27-manylinux_i686', path='/opt/python/cp27-cp27m'),
PythonConfiguration(version='2.7', identifier='cp27-manylinux_i686', path='/opt/python/cp27-cp27mu'),
PythonConfiguration(version='3.5', identifier='cp35-manylinux_i686', path='/opt/python/cp35-cp35m'),
PythonConfiguration(version='3.6', identifier='cp36-manylinux_i686', path='/opt/python/cp36-cp36m'),
PythonConfiguration(version='3.7', identifier='cp37-manylinux_i686', path='/opt/python/cp37-cp37m'),
PythonConfiguration(version='3.8', identifier='cp38-manylinux_i686', path='/opt/python/cp38-cp38'),
PythonConfiguration(version='2.7', identifier='pp27-manylinux_x86_64', path='/opt/python/pp27-pypy_73'),
PythonConfiguration(version='3.6', identifier='pp36-manylinux_x86_64', path='/opt/python/pp36-pypy36_pp73'),
PythonConfiguration(version='3.5', identifier='cp35-manylinux_aarch64', path='/opt/python/cp35-cp35m'),
PythonConfiguration(version='3.6', identifier='cp36-manylinux_aarch64', path='/opt/python/cp36-cp36m'),
PythonConfiguration(version='3.7', identifier='cp37-manylinux_aarch64', path='/opt/python/cp37-cp37m'),
PythonConfiguration(version='3.8', identifier='cp38-manylinux_aarch64', path='/opt/python/cp38-cp38'),
PythonConfiguration(version='3.5', identifier='cp35-manylinux_ppc64le', path='/opt/python/cp35-cp35m'),
PythonConfiguration(version='3.6', identifier='cp36-manylinux_ppc64le', path='/opt/python/cp36-cp36m'),
PythonConfiguration(version='3.7', identifier='cp37-manylinux_ppc64le', path='/opt/python/cp37-cp37m'),
PythonConfiguration(version='3.8', identifier='cp38-manylinux_ppc64le', path='/opt/python/cp38-cp38'),
PythonConfiguration(version='3.5', identifier='cp35-manylinux_s390x', path='/opt/python/cp35-cp35m'),
PythonConfiguration(version='3.6', identifier='cp36-manylinux_s390x', path='/opt/python/cp36-cp36m'),
PythonConfiguration(version='3.7', identifier='cp37-manylinux_s390x', path='/opt/python/cp37-cp37m'),
PythonConfiguration(version='3.8', identifier='cp38-manylinux_s390x', path='/opt/python/cp38-cp38'),
]
# skip builds as required
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(options: BuildOptions):
try:
subprocess.check_call(['docker', '--version'])
except Exception:
@@ -78,164 +86,199 @@ def build(project_dir, output_dir, test_command, before_test, test_requires, tes
file=sys.stderr)
exit(2)
python_configurations = get_python_configurations(build_selector)
python_configurations = get_python_configurations(options.build_selector)
platforms = [
('cp', 'manylinux_x86_64', manylinux_images['x86_64']),
('cp', 'manylinux_i686', manylinux_images['i686']),
('cp', 'manylinux_aarch64', manylinux_images['aarch64']),
('cp', 'manylinux_ppc64le', manylinux_images['ppc64le']),
('cp', 'manylinux_s390x', manylinux_images['s390x']),
('pp', 'manylinux_x86_64', manylinux_images['pypy_x86_64']),
('cp', 'manylinux_x86_64', options.manylinux_images['x86_64']),
('cp', 'manylinux_i686', options.manylinux_images['i686']),
('cp', 'manylinux_aarch64', options.manylinux_images['aarch64']),
('cp', 'manylinux_ppc64le', options.manylinux_images['ppc64le']),
('cp', 'manylinux_s390x', options.manylinux_images['s390x']),
('pp', 'manylinux_x86_64', options.manylinux_images['pypy_x86_64']),
]
if not os.path.realpath(options.package_dir).startswith(os.path.realpath('.')):
raise Exception('package_dir must be inside the working directory')
container_package_dir = os.path.join('/project', os.path.relpath(options.package_dir, '.'))
for implementation, platform_tag, docker_image in platforms:
platform_configs = [c for c in python_configurations if c.identifier.startswith(implementation) and c.identifier.endswith(platform_tag)]
if not platform_configs:
continue
bash_script = '''
set -o errexit
set -o xtrace
mkdir /output
cd /project
for PYBIN in {pybin_paths}; do (
# Temporary hack/workaround, putting loop body in subshell; fixed in PR #256
export PATH="$PYBIN:$PATH"
{environment_exports}
# check the active python and pip are in PYBIN
if [ "$(which pip)" != "$PYBIN/pip" ]; then
echo "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."
exit 1
fi
if [ "$(which python)" != "$PYBIN/python" ]; then
echo "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."
exit 1
fi
if [ ! -z {before_build} ]; then
sh -c {before_build}
fi
# Build the wheel
rm -rf /tmp/built_wheel
mkdir /tmp/built_wheel
pip wheel . -w /tmp/built_wheel --no-deps {build_verbosity_flag}
built_wheel=(/tmp/built_wheel/*.whl)
# repair the wheel
rm -rf /tmp/repaired_wheels
mkdir /tmp/repaired_wheels
# NOTE: 'built_wheel' here is a bash array of glob matches; "$built_wheel" returns
# the first element
if [[ "$built_wheel" == *none-any.whl ]] || [ -z {repair_command} ]; then
# pure Python wheel or empty repair command
mv "$built_wheel" /tmp/repaired_wheels
else
sh -c {repair_command} repair_command "$built_wheel"
fi
repaired_wheels=(/tmp/repaired_wheels/*.whl)
if [ ! -z {test_command} ]; then
# Set up a virtual environment to install and test from, to make sure
# there are no dependencies that were pulled in at build time.
pip install virtualenv
venv_dir=`mktemp -d`/venv
python -m virtualenv "$venv_dir"
export __CIBW_VIRTUALENV_PATH__=$venv_dir
# run the tests in a subshell to keep that `activate`
# script from polluting the env
(
source "$venv_dir/bin/activate"
echo "Running tests using `which python`"
if [ ! -z {before_test} ]; then
sh -c {before_test}
fi
# Install the wheel we just built
# Note: If auditwheel produced two wheels, it's because the earlier produced wheel
# conforms to multiple manylinux standards. These multiple versions of the wheel are
# functionally the same, differing only in name, wheel metadata, and possibly include
# different external shared libraries. so it doesn't matter which one we run the tests on.
# Let's just pick the first one.
pip install "${{repaired_wheels[0]}}"{test_extras}
# Install any requirements to run the tests
if [ ! -z "{test_requires}" ]; then
pip install {test_requires}
fi
# Run the tests from a different directory
pushd $HOME
sh -c {test_command}
popd
)
# exit if tests failed (needed for older bash versions)
if [ $? -ne 0 ]; then
exit 1;
fi
# clean up
rm -rf "$venv_dir"
fi
# we're all done here; move it to output
mv "${{repaired_wheels[@]}}" /output
for repaired_wheel in "${{repaired_wheels[@]}}"; do chown {uid}:{gid} "/output/$(basename "$repaired_wheel")"; done
) done
'''.format(
pybin_paths=' '.join(c.path + '/bin' for c in platform_configs),
test_requires=' '.join(test_requires),
test_extras=test_extras,
test_command=shlex.quote(
prepare_command(test_command, project='/project') if test_command else ''
),
before_build=shlex.quote(
prepare_command(before_build, project='/project') if before_build else ''
),
build_verbosity_flag=' '.join(get_build_verbosity_extra_flags(build_verbosity)),
repair_command=shlex.quote(
prepare_command(repair_command, wheel='"$1"', dest_dir='/tmp/repaired_wheels') if repair_command else ''
),
environment_exports='\n'.join(environment.as_shell_commands()),
uid=os.getuid(),
gid=os.getgid(),
before_test=shlex.quote(
prepare_command(before_test, project='/project') if before_test else ''
),
)
container_name = 'cibuildwheel-{}'.format(uuid.uuid4())
try:
subprocess.run(['docker', 'create',
'--env', 'CIBUILDWHEEL',
'--name', container_name,
'-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)
call(['docker', 'create',
'--env', 'CIBUILDWHEEL',
'--name', container_name,
'-i',
'-v', '/:/host', # ignored on CircleCI
docker_image,
'/bin/bash'])
call(['docker', 'cp', '.', container_name + ':/project'])
call(['docker', 'start', container_name])
for config in platform_configs:
if options.dependency_constraints:
constraints_file = options.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 xtrace
mkdir -p /output
cd /project
PYBIN="{config_python_bin}"
export PATH="$PYBIN:$PATH"
{environment_exports}
# check the active python and pip are in PYBIN
if [ "$(which pip)" != "$PYBIN/pip" ]; then
echo "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."
exit 1
fi
if [ "$(which python)" != "$PYBIN/python" ]; then
echo "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."
exit 1
fi
if [ ! -z {before_build} ]; then
sh -c {before_build}
fi
# Build the wheel
rm -rf /tmp/built_wheel
mkdir /tmp/built_wheel
pip wheel {package_dir} -w /tmp/built_wheel --no-deps {build_verbosity_flag}
built_wheel=(/tmp/built_wheel/*.whl)
# repair the wheel
rm -rf /tmp/repaired_wheels
mkdir /tmp/repaired_wheels
# NOTE: 'built_wheel' here is a bash array of glob matches; "$built_wheel" returns
# the first element
if [[ "$built_wheel" == *none-any.whl ]] || [ -z {repair_command} ]; then
# pure Python wheel or empty repair command
mv "$built_wheel" /tmp/repaired_wheels
else
sh -c {repair_command} repair_command "$built_wheel"
fi
repaired_wheels=(/tmp/repaired_wheels/*.whl)
if [ ! -z {test_command} ]; then
# Set up a virtual environment to install and test from, to make sure
# there are no dependencies that were pulled in at build time.
pip install {dependency_install_flags} virtualenv
venv_dir=`mktemp -d`/venv
python -m virtualenv --no-download "$venv_dir"
export __CIBW_VIRTUALENV_PATH__=$venv_dir
# run the tests in a subshell to keep that `activate`
# script from polluting the env
(
source "$venv_dir/bin/activate"
echo "Running tests using `which python`"
if [ ! -z {before_test} ]; then
sh -c {before_test}
fi
# Install the wheel we just built
# Note: If auditwheel produced two wheels, it's because the earlier produced wheel
# conforms to multiple manylinux standards. These multiple versions of the wheel are
# functionally the same, differing only in name, wheel metadata, and possibly include
# different external shared libraries. so it doesn't matter which one we run the tests on.
# Let's just pick the first one.
pip install "${{repaired_wheels[0]}}"{test_extras}
# Install any requirements to run the tests
if [ ! -z "{test_requires}" ]; then
pip install {test_requires}
fi
# Run the tests from a different directory
pushd $HOME
sh -c {test_command}
popd
)
# exit if tests failed (needed for older bash versions)
if [ $? -ne 0 ]; then
exit 1;
fi
# clean up
rm -rf "$venv_dir"
fi
# we're all done here; move it to output
mv "${{repaired_wheels[@]}}" /output
for repaired_wheel in "${{repaired_wheels[@]}}"; do
chown {uid}:{gid} "/output/$(basename "$repaired_wheel")"
done
'''.format(
config_python_bin=config.path + '/bin',
package_dir=container_package_dir,
test_requires=' '.join(options.test_requires),
test_extras=options.test_extras,
test_command=shlex.quote(
prepare_command(options.test_command, project='/project', package=container_package_dir) if options.test_command else ''
),
before_build=shlex.quote(
prepare_command(options.before_build, project='/project', package=container_package_dir) if options.before_build else ''
),
build_verbosity_flag=' '.join(get_build_verbosity_extra_flags(options.build_verbosity)),
repair_command=shlex.quote(
prepare_command(options.repair_command, wheel='"$1"', dest_dir='/tmp/repaired_wheels') if options.repair_command else ''
),
environment_exports='\n'.join(options.environment.as_shell_commands()),
uid=os.getuid(),
gid=os.getgid(),
before_test=shlex.quote(
prepare_command(options.before_test, project='/project', package=container_package_dir) if options.before_test else ''
),
dependency_install_flags='-c /constraints.txt' if options.dependency_constraints else '',
)
)
# copy the output back into the host
call(['docker', 'cp',
container_name + ':/output/.',
os.path.abspath(options.output_dir)])
except subprocess.CalledProcessError as error:
troubleshoot(project_dir, error)
troubleshoot(options.package_dir, error)
exit(1)
finally:
# 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):
if (isinstance(error, subprocess.CalledProcessError) and 'start' in error.cmd):
def troubleshoot(package_dir, error):
if (isinstance(error, subprocess.CalledProcessError) and 'exec' in error.cmd):
# the bash script failed
print('Checking for common errors...')
so_files = []
for root, dirs, files in os.walk(project_dir):
for root, dirs, files in os.walk(package_dir):
for name in files:
_, ext = os.path.splitext(name)
if ext == '.so':
+97 -86
View File
@@ -11,6 +11,8 @@ from .util import (
download,
get_build_verbosity_extra_flags,
prepare_command,
get_pip_script,
BuildOptions
)
@@ -27,13 +29,15 @@ def call(args, env=None, cwd=None, shell=False):
def get_python_configurations(build_selector):
PythonConfiguration = namedtuple('PythonConfiguration', ['version', 'identifier', 'url'])
python_configurations = [
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'),
# CPython
PythonConfiguration(version='2.7', identifier='cp27-macosx_x86_64', url='https://www.python.org/ftp/python/2.7.18/python-2.7.18-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.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.7/python-3.7.7-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'),
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'),
# PyPy
PythonConfiguration(version='2.7', identifier='pp27-macosx_x86_64', url='https://downloads.python.org/pypy/pypy2.7-v7.3.1-osx64.tar.bz2'),
PythonConfiguration(version='3.6', identifier='pp36-macosx_x86_64', url='https://downloads.python.org/pypy/pypy3.6-v7.3.1-osx64.tar.bz2'),
]
# skip builds as required
@@ -70,7 +74,7 @@ def install_cpython(version, url):
call(['sudo', 'installer', '-pkg', '/tmp/Python.pkg', '-target', '/'])
# patch open ssl
if version == '3.5':
open_ssl_patch_url = 'https://github.com/mayeut/patch-macos-python-openssl/releases/download/v1.0.2t/patch-macos-python-%s-openssl-v1.0.2t.tar.gz' % version
open_ssl_patch_url = 'https://github.com/mayeut/patch-macos-python-openssl/releases/download/v1.0.2u/patch-macos-python-%s-openssl-v1.0.2u.tar.gz' % version
download(open_ssl_patch_url, '/tmp/python-patch.tar.gz')
call(['sudo', 'tar', '-C', '/Library/Frameworks/Python.framework/Versions/{}/'.format(version), '-xmf', '/tmp/python-patch.tar.gz'])
@@ -91,12 +95,6 @@ def install_pypy(version, url):
download(url, 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
if version.endswith("-v7.3.0") and version[0] == '3':
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')
call(['patch', sysconfigdata_file, patch_file, '-N']) # Always has nonzero return code
installation_bin_path = os.path.join(installation_path, 'bin')
python_executable = 'pypy3' if version[0] == '3' else 'pypy'
pip_executable = 'pip3' if version[0] == '3' else 'pip'
@@ -105,104 +103,113 @@ def install_pypy(version, url):
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):
abs_project_dir = os.path.abspath(project_dir)
def setup_python(python_configuration, dependency_constraint_flags, environment):
if python_configuration.identifier.startswith('cp'):
installation_bin_path = install_cpython(python_configuration.version, python_configuration.url)
elif python_configuration.identifier.startswith('pp'):
installation_bin_path = install_pypy(python_configuration.version, python_configuration.url)
else:
raise ValueError("Unknown Python implementation")
env = os.environ.copy()
env['PATH'] = os.pathsep.join([
SYMLINKS_DIR,
installation_bin_path,
env['PATH'],
])
# Fix issue with site.py setting the wrong `sys.prefix`, `sys.exec_prefix`,
# `sys.path`, ... for PyPy: https://foss.heptapod.net/pypy/pypy/issues/3175
# Also fix an issue with the shebang of installed scripts inside the
# testing virtualenv- see https://github.com/theacodes/nox/issues/44 and
# https://github.com/pypa/virtualenv/issues/620
# Also see https://github.com/python/cpython/pull/9516
env.pop('__PYVENV_LAUNCHER__', None)
env = environment.as_dictionary(prev_environment=env)
# check what version we're on
call(['which', 'python'], env=env)
call(['python', '--version'], env=env)
which_python = subprocess.check_output(['which', 'python'], env=env, universal_newlines=True).strip()
if which_python != '/tmp/cibw_bin/python':
print("cibuildwheel: python available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert python above it.", file=sys.stderr)
exit(1)
# install pip & wheel
call(['python', get_pip_script] + dependency_constraint_flags, env=env, cwd="/tmp")
assert os.path.exists(os.path.join(installation_bin_path, 'pip'))
call(['which', 'pip'], env=env)
call(['pip', '--version'], env=env)
which_pip = subprocess.check_output(['which', 'pip'], env=env, universal_newlines=True).strip()
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)
exit(1)
call(['pip', 'install', '--upgrade', 'setuptools', 'wheel', 'delocate'] + dependency_constraint_flags, env=env)
# Set MACOSX_DEPLOYMENT_TARGET to 10.9, if the user didn't set it.
# CPython 3.5 defaults to 10.6, and pypy defaults to 10.7, causing
# inconsistencies if it's left unset.
env.setdefault('MACOSX_DEPLOYMENT_TARGET', '10.9')
if python_configuration.version == '3.5':
# Cross-compilation platform override - CPython 3.5 has an
# i386/x86_64 version of Python, but we only want a x64_64 build
env.setdefault('_PYTHON_HOST_PLATFORM', 'macosx-10.9-x86_64')
# https://github.com/python/cpython/blob/a5ed2fe0eedefa1649aa93ee74a0bafc8e628a10/Lib/_osx_support.py#L260
env.setdefault('ARCHFLAGS', '-arch x86_64')
return env
def build(options: BuildOptions):
temp_dir = tempfile.mkdtemp(prefix='cibuildwheel')
built_wheel_dir = os.path.join(temp_dir, 'built_wheel')
repaired_wheel_dir = os.path.join(temp_dir, 'repaired_wheel')
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)
python_configurations = get_python_configurations(options.build_selector)
for config in python_configurations:
if config.identifier.startswith('cp'):
installation_bin_path = install_cpython(config.version, config.url)
elif config.identifier.startswith('pp'):
installation_bin_path = install_pypy(config.version, config.url)
else:
raise ValueError("Unknown Python implementation")
dependency_constraint_flags = []
if options.dependency_constraints:
dependency_constraint_flags = [
'-c', options.dependency_constraints.get_for_python_version(config.version)
]
env = os.environ.copy()
env['PATH'] = os.pathsep.join([
SYMLINKS_DIR,
installation_bin_path,
env['PATH'],
])
# Fix issue with site.py setting the wrong `sys.prefix`, `sys.exec_prefix`,
# `sys.path`, ... for PyPy: https://foss.heptapod.net/pypy/pypy/issues/3175
# Also fix an issue with the shebang of installed scripts inside the
# testing virtualenv- see https://github.com/theacodes/nox/issues/44 and
# https://github.com/pypa/virtualenv/issues/620
# Also see https://github.com/python/cpython/pull/9516
env.pop('__PYVENV_LAUNCHER__', None)
env = environment.as_dictionary(prev_environment=env)
# check what version we're on
call(['which', 'python'], env=env)
call(['python', '--version'], env=env)
which_python = subprocess.check_output(['which', 'python'], env=env, universal_newlines=True).strip()
if which_python != '/tmp/cibw_bin/python':
print("cibuildwheel: python available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert python above it.", file=sys.stderr)
exit(1)
# install pip & wheel
call(['python', get_pip_script], env=env, cwd="/tmp")
assert os.path.exists(os.path.join(installation_bin_path, 'pip'))
call(['which', 'pip'], env=env)
call(['pip', '--version'], env=env)
which_pip = subprocess.check_output(['which', 'pip'], env=env, universal_newlines=True).strip()
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)
exit(1)
call(['pip', 'install', '--upgrade', 'setuptools', 'wheel', 'delocate'], env=env)
# setup target platform, only required for python 3.5
if config.version == '3.5':
if '_PYTHON_HOST_PLATFORM' not in env:
# cross-compilation platform override
env['_PYTHON_HOST_PLATFORM'] = 'macosx-10.9-x86_64'
if 'ARCHFLAGS' not in env:
# https://github.com/python/cpython/blob/a5ed2fe0eedefa1649aa93ee74a0bafc8e628a10/Lib/_osx_support.py#L260
env['ARCHFLAGS'] = '-arch x86_64'
if 'MACOSX_DEPLOYMENT_TARGET' not in env:
env['MACOSX_DEPLOYMENT_TARGET'] = '10.9'
env = setup_python(config, dependency_constraint_flags, options.environment)
# run the before_build command
if before_build:
before_build_prepared = prepare_command(before_build, project=abs_project_dir)
if options.before_build:
before_build_prepared = prepare_command(options.before_build, project='.', package=options.package_dir)
call(before_build_prepared, env=env, shell=True)
# build the wheel
if os.path.exists(built_wheel_dir):
shutil.rmtree(built_wheel_dir)
os.makedirs(built_wheel_dir)
call(['pip', 'wheel', abs_project_dir, '-w', built_wheel_dir, '--no-deps'] + get_build_verbosity_extra_flags(build_verbosity), env=env)
call(['pip', 'wheel', options.package_dir, '-w', built_wheel_dir, '--no-deps'] + get_build_verbosity_extra_flags(options.build_verbosity), env=env)
built_wheel = glob(os.path.join(built_wheel_dir, '*.whl'))[0]
# repair the wheel
if os.path.exists(repaired_wheel_dir):
shutil.rmtree(repaired_wheel_dir)
os.makedirs(repaired_wheel_dir)
if built_wheel.endswith('none-any.whl') or not repair_command:
if built_wheel.endswith('none-any.whl') or not options.repair_command:
# pure Python wheel or empty repair command
shutil.move(built_wheel, repaired_wheel_dir)
else:
repair_command_prepared = prepare_command(repair_command, wheel=built_wheel, dest_dir=repaired_wheel_dir)
repair_command_prepared = prepare_command(options.repair_command, wheel=built_wheel, dest_dir=repaired_wheel_dir)
call(repair_command_prepared, env=env, shell=True)
repaired_wheel = glob(os.path.join(repaired_wheel_dir, '*.whl'))[0]
if test_command:
if options.test_command:
# set up a virtual environment to install and test from, to make sure
# there are no dependencies that were pulled in at build time.
call(['pip', 'install', 'virtualenv'], env=env)
call(['pip', 'install', 'virtualenv'] + dependency_constraint_flags, env=env)
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['PATH'] = os.pathsep.join([
@@ -214,26 +221,30 @@ def build(project_dir, output_dir, test_command, before_test, test_requires, tes
# check that we are using the Python from the virtual environment
call(['which', 'python'], env=virtualenv_env)
if before_test:
before_test_prepared = prepare_command(before_test, project=abs_project_dir)
if options.before_test:
before_test_prepared = prepare_command(options.before_test, project='.', package=options.package_dir)
call(before_test_prepared, env=virtualenv_env, shell=True)
# install the wheel
call(['pip', 'install', repaired_wheel + test_extras], env=virtualenv_env)
call(['pip', 'install', repaired_wheel + options.test_extras], env=virtualenv_env)
# test the wheel
if test_requires:
call(['pip', 'install'] + test_requires, env=virtualenv_env)
if options.test_requires:
call(['pip', 'install'] + options.test_requires, env=virtualenv_env)
# run the tests from $HOME, with an absolute path in the command
# (this ensures that Python runs the tests against the installed wheel
# and not the repo code)
test_command_prepared = prepare_command(test_command, project=abs_project_dir)
test_command_prepared = prepare_command(
options.test_command,
project=os.path.abspath('.'),
package=os.path.abspath(options.package_dir)
)
call(test_command_prepared, cwd=os.environ['HOME'], env=virtualenv_env, shell=True)
# clean up
shutil.rmtree(venv_dir)
# we're all done here; move it to output (overwrite existing)
dst = os.path.join(output_dir, os.path.basename(repaired_wheel))
dst = os.path.join(options.output_dir, os.path.basename(repaired_wheel))
shutil.move(repaired_wheel, dst)
@@ -0,0 +1,26 @@
#
# This file is autogenerated by pip-compile
# To update, run:
#
# bin/update_constraints.py
#
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.6.0 # via importlib-resources, virtualenv
importlib-resources==1.5.0 # 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.18 # 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.1 # via -r cibuildwheel/resources/constraints.in
setuptools==44.1.0 # via -r cibuildwheel/resources/constraints.in
@@ -0,0 +1,20 @@
#
# This file is autogenerated by pip-compile
# To update, run:
#
# bin/update_constraints.py
#
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.6.0 # via importlib-resources, virtualenv
importlib-resources==1.5.0 # via virtualenv
six==1.14.0 # via virtualenv
virtualenv==20.0.18 # 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.1 # via -r cibuildwheel/resources/constraints.in
setuptools==46.1.3 # via -r cibuildwheel/resources/constraints.in
@@ -0,0 +1,20 @@
#
# This file is autogenerated by pip-compile
# To update, run:
#
# bin/update_constraints.py
#
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.6.0 # via importlib-resources, virtualenv
importlib-resources==1.5.0 # via virtualenv
six==1.14.0 # via virtualenv
virtualenv==20.0.18 # 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.1 # via -r cibuildwheel/resources/constraints.in
setuptools==46.1.3 # via -r cibuildwheel/resources/constraints.in
+5
View File
@@ -0,0 +1,5 @@
pip
setuptools
wheel
delocate
virtualenv
+19
View File
@@ -0,0 +1,19 @@
#
# This file is autogenerated by pip-compile
# To update, run:
#
# bin/update_constraints.py
#
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.6.0 # via virtualenv
six==1.14.0 # via virtualenv
virtualenv==20.0.18 # 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.1 # via -r cibuildwheel/resources/constraints.in
setuptools==46.1.3 # 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-04-25-37c204c
manylinux2010 = quay.io/pypa/manylinux2010_x86_64:2020-04-29-0e1afc5
manylinux2014 = quay.io/pypa/manylinux2014_x86_64:2020-05-01-b37d76b
[i686]
manylinux1 = quay.io/pypa/manylinux1_i686:2020-04-25-37c204c
manylinux2010 = quay.io/pypa/manylinux2010_i686:2020-04-29-0e1afc5
manylinux2014 = quay.io/pypa/manylinux2014_i686:2020-05-01-b37d76b
[pypy_x86_64]
manylinux2010 = pypywheels/manylinux2010-pypy_x86_64:2020-04-25-eb2cdff
[aarch64]
manylinux2014 = quay.io/pypa/manylinux2014_aarch64:2020-05-01-b37d76b
[ppc64le]
manylinux2014 = quay.io/pypa/manylinux2014_ppc64le:2020-05-01-b37d76b
[s390x]
manylinux2014 = quay.io/pypa/manylinux2014_s390x:2020-05-01-b37d76b
+53
View File
@@ -2,6 +2,9 @@ import os
import urllib.request
from fnmatch import fnmatch
from time import sleep
from typing import NamedTuple, List, Optional, Dict
from .environment import ParsedEnvironment
def prepare_command(command, **kwargs):
@@ -76,3 +79,53 @@ def download(url, dest):
file.write(response.read())
finally:
response.close()
class DependencyConstraints:
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
BuildOptions = NamedTuple("BuildOptions", [
("package_dir", str),
("output_dir", str),
("test_command", Optional[str]),
("test_requires", List[str]),
("test_extras", str),
("before_build", Optional[str]),
("build_verbosity", int),
("build_selector", BuildSelector),
("repair_command", str),
("environment", ParsedEnvironment),
("before_test", str),
("dependency_constraints", Optional[DependencyConstraints]),
("manylinux_images", Optional[Dict[str, str]]),
])
"""
Replace this definition with a class-style NamedTuple in the
PEP526 style when Python 3.5 host support is dropped
"""
resources_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), 'resources'))
get_pip_script = os.path.join(resources_dir, 'get-pip.py')
+97 -71
View File
@@ -11,6 +11,8 @@ from .util import (
download,
get_build_verbosity_extra_flags,
prepare_command,
get_pip_script,
BuildOptions
)
@@ -33,18 +35,20 @@ def get_nuget_args(version, arch):
def get_python_configurations(build_selector):
PythonConfiguration = namedtuple('PythonConfiguration', ['version', 'arch', 'identifier', 'url'])
python_configurations = [
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),
# CPython
PythonConfiguration(version='2.7.18', arch='32', identifier='cp27-win32', url=None),
PythonConfiguration(version='2.7.18', 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='64', identifier='cp35-win_amd64', url=None),
PythonConfiguration(version='3.6.8', arch='32', identifier='cp36-win32', url=None),
PythonConfiguration(version='3.6.8', arch='64', identifier='cp36-win_amd64', url=None),
PythonConfiguration(version='3.7.6', arch='32', identifier='cp37-win32', url=None),
PythonConfiguration(version='3.7.6', arch='64', identifier='cp37-win_amd64', url=None),
PythonConfiguration(version='3.7.7', arch='32', identifier='cp37-win32', url=None),
PythonConfiguration(version='3.7.7', 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='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'),
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'),
# PyPy
PythonConfiguration(version='2.7', arch='32', identifier='pp27-win32', url='https://downloads.python.org/pypy/pypy2.7-v7.3.1-win32.zip'),
PythonConfiguration(version='3.6', arch='32', identifier='pp36-win32', url='https://downloads.python.org/pypy/pypy3.6-v7.3.1-win32.zip'),
]
if IS_RUNNING_ON_TRAVIS:
@@ -82,12 +86,62 @@ def install_pypy(version, arch, url):
extract_zip(pypy_zip, os.path.dirname(installation_path))
pypy_exe = 'pypy3.exe' if version[0] == '3' else 'pypy.exe'
shell(['mklink', os.path.join(installation_path, 'python.exe'), os.path.join(installation_path, pypy_exe)])
shell(['mklink', '/d', os.path.join(installation_path, 'Scripts'), os.path.join(installation_path, 'bin')])
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):
abs_project_dir = os.path.abspath(project_dir)
def setup_python(python_configuration, dependency_constraint_flags, environment):
nuget = 'C:\\cibw\\nuget.exe'
if not os.path.exists(nuget):
download('https://dist.nuget.org/win-x86-commandline/latest/nuget.exe', nuget)
if python_configuration.identifier.startswith('cp'):
installation_path = install_cpython(python_configuration.version, python_configuration.arch, nuget)
elif python_configuration.identifier.startswith('pp'):
installation_path = install_pypy(python_configuration.version, python_configuration.arch, python_configuration.url)
else:
raise ValueError("Unknown Python implementation")
assert os.path.exists(os.path.join(installation_path, 'python.exe'))
# set up PATH and environment variables for run_with_env
env = os.environ.copy()
env['PYTHON_VERSION'] = python_configuration.version
env['PYTHON_ARCH'] = python_configuration.arch
env['PATH'] = os.pathsep.join([
installation_path,
os.path.join(installation_path, 'Scripts'),
env['PATH']
])
# update env with results from CIBW_ENVIRONMENT
env = environment.as_dictionary(prev_environment=env)
# for the logs - check we're running the right version of python
shell(['where', 'python'], env=env)
shell(['python', '--version'], env=env)
shell(['python', '-c', '"import struct; print(struct.calcsize(\'P\') * 8)"'], env=env)
where_python = subprocess.check_output(['where', 'python'], env=env, universal_newlines=True).splitlines()[0].strip()
if where_python != os.path.join(installation_path, 'python.exe'):
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)
# make sure pip is installed
if not os.path.exists(os.path.join(installation_path, 'Scripts', 'pip.exe')):
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'))
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'):
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)
# prepare the Python environment
shell(['python', '-m', 'pip', 'install', '--upgrade', 'pip'] + dependency_constraint_flags, env=env)
shell(['pip', '--version'], env=env)
shell(['pip', 'install', '--upgrade', 'setuptools', 'wheel'] + dependency_constraint_flags, env=env)
return env
def build(options: BuildOptions):
temp_dir = tempfile.mkdtemp(prefix='cibuildwheel')
built_wheel_dir = os.path.join(temp_dir, 'built_wheel')
repaired_wheel_dir = os.path.join(temp_dir, 'repaired_wheel')
@@ -95,87 +149,51 @@ def build(project_dir, output_dir, test_command, before_test, test_requires, tes
# install nuget as best way to provide python
nuget = 'C:\\cibw\\nuget.exe'
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(options.build_selector)
for config in python_configurations:
dependency_constraint_flags = []
if options.dependency_constraints:
dependency_constraint_flags = [
'-c', options.dependency_constraints.get_for_python_version(config.version)
]
# install Python
if config.identifier.startswith('cp'):
installation_path = install_cpython(config.version, config.arch, nuget)
elif config.identifier.startswith('pp'):
installation_path = install_pypy(config.version, config.arch, config.url)
else:
raise ValueError("Unknown Python implementation")
assert os.path.exists(os.path.join(installation_path, 'python.exe'))
# set up PATH and environment variables for run_with_env
env = os.environ.copy()
env['PYTHON_VERSION'] = config.version
env['PYTHON_ARCH'] = config.arch
env['PATH'] = os.pathsep.join([
installation_path,
os.path.join(installation_path, 'Scripts'),
env['PATH']
])
# update env with results from CIBW_ENVIRONMENT
env = environment.as_dictionary(prev_environment=env)
# for the logs - check we're running the right version of python
shell(['where', 'python'], env=env)
shell(['python', '--version'], env=env)
shell(['python', '-c', '"import struct; print(struct.calcsize(\'P\') * 8)"'], env=env)
where_python = subprocess.check_output(['where', 'python'], env=env, universal_newlines=True).splitlines()[0].strip()
if where_python != os.path.join(installation_path, 'python.exe'):
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)
# make sure pip is installed
if not os.path.exists(os.path.join(installation_path, 'Scripts', 'pip.exe')):
shell(['python', get_pip_script], env=env, cwd="C:\\cibw")
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()
if where_pip.strip() != os.path.join(installation_path, 'Scripts', 'pip.exe'):
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)
# prepare the Python environment
shell(['python', '-m', 'pip', 'install', '--upgrade', 'pip'], env=env)
shell(['pip', '--version'], env=env)
shell(['pip', 'install', '--upgrade', 'setuptools', 'wheel'], env=env)
env = setup_python(config, dependency_constraint_flags, options.environment)
# run the before_build command
if before_build:
before_build_prepared = prepare_command(before_build, project=abs_project_dir)
if options.before_build:
before_build_prepared = prepare_command(options.before_build, project='.', package=options.package_dir)
shell([before_build_prepared], env=env)
# build the wheel
if os.path.exists(built_wheel_dir):
shutil.rmtree(built_wheel_dir)
os.makedirs(built_wheel_dir)
shell(['pip', 'wheel', abs_project_dir, '-w', built_wheel_dir, '--no-deps'] + get_build_verbosity_extra_flags(build_verbosity), env=env)
shell(['pip', 'wheel', options.package_dir, '-w', built_wheel_dir, '--no-deps'] + get_build_verbosity_extra_flags(options.build_verbosity), env=env)
built_wheel = glob(os.path.join(built_wheel_dir, '*.whl'))[0]
# repair the wheel
if os.path.exists(repaired_wheel_dir):
shutil.rmtree(repaired_wheel_dir)
os.makedirs(repaired_wheel_dir)
if built_wheel.endswith('none-any.whl') or not repair_command:
if built_wheel.endswith('none-any.whl') or not options.repair_command:
# pure Python wheel or empty repair command
shutil.move(built_wheel, repaired_wheel_dir)
else:
repair_command_prepared = prepare_command(repair_command, wheel=built_wheel, dest_dir=repaired_wheel_dir)
repair_command_prepared = prepare_command(options.repair_command, wheel=built_wheel, dest_dir=repaired_wheel_dir)
shell([repair_command_prepared], env=env)
repaired_wheel = glob(os.path.join(repaired_wheel_dir, '*.whl'))[0]
if test_command:
if options.test_command:
# set up a virtual environment to install and test from, to make sure
# there are no dependencies that were pulled in at build time.
shell(['pip', 'install', 'virtualenv'], env=env)
shell(['pip', 'install', 'virtualenv'] + dependency_constraint_flags, env=env)
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()
@@ -192,28 +210,36 @@ def build(project_dir, output_dir, test_command, before_test, test_requires, tes
# check that we are using the Python from the virtual environment
shell(['which', 'python'], env=virtualenv_env)
if before_test:
before_test_prepared = prepare_command(before_test, project=abs_project_dir)
if options.before_test:
before_test_prepared = prepare_command(
options.before_test,
project='.',
package=options.package_dir
)
shell([before_test_prepared], env=virtualenv_env)
# install the wheel
shell(['pip', 'install', repaired_wheel + test_extras], env=virtualenv_env)
shell(['pip', 'install', repaired_wheel + options.test_extras], env=virtualenv_env)
# test the wheel
if test_requires:
shell(['pip', 'install'] + test_requires, env=virtualenv_env)
if options.test_requires:
shell(['pip', 'install'] + options.test_requires, env=virtualenv_env)
# run the tests from c:\, with an absolute path in the command
# (this ensures that Python runs the tests against the installed wheel
# and not the repo code)
test_command_prepared = prepare_command(test_command, project=abs_project_dir)
test_command_prepared = prepare_command(
options.test_command,
project=os.path.abspath('.'),
package=os.path.abspath(options.package_dir)
)
shell([test_command_prepared], cwd='c:\\', env=virtualenv_env)
# clean up
shutil.rmtree(venv_dir)
# we're all done here; move it to output (remove if already exists)
dst = os.path.join(output_dir, os.path.basename(repaired_wheel))
dst = os.path.join(options.output_dir, os.path.basename(repaired_wheel))
if os.path.isfile(dst):
os.remove(dst)
shutil.move(repaired_wheel, dst)
+6 -4
View File
@@ -7,7 +7,7 @@ Building Python wheels with modern C++ standards (C++11 and later) requires a fe
## Python 2.7 and C++17
The Python 2.7 header files use the `register` keyword, which is [reserved and unused from C+17 onwards](https://en.cppreference.com/w/cpp/keyword/register). Compiling a wheel for Python 2.7 with the C++17 standard is still possible to allow usage of `register` using proper flag `-Wno-register` for gcc/clang and `/wd5033` for MSVC.
The Python 2.7 header files use the `register` keyword, which is [reserved and unused from C+17 onwards](https://en.cppreference.com/w/cpp/keyword/register). Compiling a wheel for Python 2.7 with the C++17 standard is still possible to allow usage of `register` using proper flag `-Wno-register` for gcc/clang and `/wd5033` for MSVC.
## manylinux1 and C++14
The default `manylinux1` image (based on CentOS 5) contains a version of GCC and libstdc++ that only supports C++11 and earlier standards. There are however ways to compile wheels with the C++14 standard (and later): https://github.com/pypa/manylinux/issues/118
@@ -16,15 +16,17 @@ The default `manylinux1` image (based on CentOS 5) contains a version of GCC and
## macOS and deployment target versions
OS X/macOS allows you to specify a so-called "deployment target" version that will ensure backwards compatibility with older versions of macOS. One way to do this is by setting the `MACOSX_DEPLOYMENT_TARGET` environment variable. If not set, Python will set this variable to the version the Python distribution itself was compiled on (10.6 or 10.9, for the python.org packages), when creating the wheel.
OS X/macOS allows you to specify a so-called "deployment target" version that will ensure backwards compatibility with older versions of macOS. One way to do this is by setting the `MACOSX_DEPLOYMENT_TARGET` environment variable.
However, to enable modern C++ standards, the deploment target needs to be set high enough (since older OS X/macOS versions did not have the necessary modern C++ standard library).
To get C++11 and C++14 support, `MACOSX_DEPLOYMENT_TARGET` needs to be set to (at least) `"10.9"`. By default, `cibuildwheel` already does this, building 64-bit-only wheels for macOS 10.9 and later.
To get C++17 support, set `MACOSX_DEPLOYMENT_TARGET` to (at least) `"10.13"` or `"10.14"` (macOS 10.13 offers partial C++17 support; e.g., the filesystem header is in experimental, offering `#include <experimental/filesystem>` instead of `#include <filesystem>`; macOS 10.14 has full C++17 support).
To get C++17 support, Xcode 9.3+ is needed, requiring at least macOS 10.13 on the build machine. To use C++17 library features and link against the C++ runtime library, set `MACOSX_DEPLOYMENT_TARGET` to `"10.13"` or `"10.14"` (or higher) - macOS 10.13 offers partial C++17 support (e.g., the filesystem header is in experimental, offering `#include <experimental/filesystem>` instead of `#include <filesystem>`); macOS 10.14 has full C++17 support.
For more details see https://en.cppreference.com/w/cpp/compiler_support and https://xcodereleases.com/: Xcode 10 needs macOS 10.13 and Xcode 11 needs macOS 10.14.
However, if only C++17 compiler and standard template library (STL) features are used (not needing a C++17 runtime) it might be possible to set `MACOSX_DEPLOYMENT_TARGET` to a lower value, such as `"10.9"`. To find out if this is the case, try compiling and running with a lower `MACOSX_DEPLOYMENT_TARGET`: if C++17 features are used that require a more recent deployment target, building the wheel should fail.
For more details see https://en.cppreference.com/w/cpp/compiler_support, https://en.wikipedia.org/wiki/Xcode, and https://xcodereleases.com/: Xcode 10 needs macOS 10.13 and Xcode 11 needs macOS 10.14.
## Windows and Python 2.7
+92 -22
View File
@@ -14,16 +14,16 @@ your CI config file:
```yaml
env:
global:
- CIBW_TEST_REQUIRES=nose
- CIBW_TEST_COMMAND="nosetests {project}/tests"
- CIBW_TEST_REQUIRES=pytest
- CIBW_TEST_COMMAND="pytest {project}/tests"
```
> appveyor.yml ([docs](https://www.appveyor.com/docs/build-configuration/#environment-variables))
```yaml
environment:
global:
CIBW_TEST_REQUIRES: nose
CIBW_TEST_COMMAND: "nosetests {project}\\tests"
CIBW_TEST_REQUIRES: pytest
CIBW_TEST_COMMAND: "pytest {project}\\tests"
```
> .circleci/config.yml ([docs](https://circleci.com/docs/2.0/configuration-reference/#environment))
@@ -31,15 +31,22 @@ environment:
jobs:
job_name:
environment:
CIBW_TEST_REQUIRES: nose
CIBW_TEST_COMMAND: "nosetests {project}/tests"
CIBW_TEST_REQUIRES: pytest
CIBW_TEST_COMMAND: "pytest {project}/tests"
```
> azure-pipelines.yml ([docs](https://docs.microsoft.com/en-us/azure/devops/pipelines/process/variables))
```yaml
variables:
CIBW_TEST_REQUIRES: nose
CIBW_TEST_COMMAND: "nosetests {project}/tests"
CIBW_TEST_REQUIRES: pytest
CIBW_TEST_COMMAND: "pytest {project}/tests"
```
> .github/workflows/*.yml ([docs](https://help.github.com/en/actions/configuring-and-managing-workflows/using-environment-variables)) (can be global, in job, or in step)
```yaml
env:
CIBW_TEST_REQUIRES: pytest
CIBW_TEST_COMMAND: "pytest {project}/tests"
```
@@ -192,7 +199,7 @@ A shell command to run before building the wheel. This option allows you to run
If dependencies are required to build your wheel (for example if you include a header from a Python module), set this to `pip install .`, and the dependencies will be installed automatically by pip. However, this means your package will be built twice - if your package takes a long time to build, you might wish to manually list the dependencies here instead.
The active Python binary can be accessed using `python`, and pip with `pip`; `cibuildwheel` makes sure the right version of Python and pip will be executed. `{project}` can be used as a placeholder for the absolute path to the project's root and will be replaced by `cibuildwheel`.
The active Python binary can be accessed using `python`, and pip with `pip`; `cibuildwheel` makes sure the right version of Python and pip will be executed. The placeholder `{package}` can be used here; it will be replaced by the path to the package being built by `cibuildwheel`.
The command is run in a shell, so you can write things like `cmd1 && cmd2`.
@@ -210,8 +217,11 @@ CIBW_BEFORE_BUILD: pip install pybind11
# chain commands using &&
CIBW_BEFORE_BUILD: yum install -y libffi-dev && pip install .
# run a script that's inside your repo
# run a script that's inside your project
CIBW_BEFORE_BUILD: bash scripts/prepare_for_build.sh
# if cibuildwheel is called with a package_dir argument, it's available as {package}
CIBW_BEFORE_BUILD: "{package}/bin/prepare_for_build.sh"
```
@@ -253,7 +263,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).
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.
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).
@@ -276,17 +286,75 @@ CIBW_MANYLINUX_X86_64_IMAGE: manylinux2014
CIBW_MANYLINUX_I686_IMAGE: manylinux2014
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
CIBW_MANYLINUX_X86_64_IMAGE: dockcross/manylinux-x64
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
### `CIBW_TEST_COMMAND` {: #test-command}
> Execute a shell command to test each built wheel
Shell command to run tests after the build. The wheel will be installed automatically and available for import from the tests. `{project}` can be used as a placeholder for the absolute path to the project's root and will be replaced by `cibuildwheel`.
Shell command to run tests after the build. The wheel will be installed automatically and available for import from the tests. To ensure the wheel is imported by your tests (instead of your source copy), tests are run from a different directory. Use the placeholders `{project}` and `{package}` when specifying paths in your project.
- `{project}` is an absolute path to the project root - the working directory where cibuildwheel was called.
- `{package}` is the path to the package being built - the `package_dir` argument supplied to cibuildwheel on the command line.
The command is run in a shell, so you can write things like `cmd1 && cmd2`.
@@ -299,8 +367,8 @@ Platform-specific variants also available:<br/>
# run the project tests against the installed wheel using `nose`
CIBW_TEST_COMMAND: nosetests {project}/tests
# run the project tests using `pytest`
CIBW_TEST_COMMAND: nosetests {project}/tests
# run the package tests using `pytest`
CIBW_TEST_COMMAND: pytest {package}/tests
```
@@ -349,7 +417,7 @@ CIBW_TEST_EXTRAS: test,qt
A shell command to run in **each** test virtual environment, before your wheel is installed and tested. This is useful if you need to install a non pip package, change values of environment variables
or perform multi step pip installation (e.g. installing `scikit-build` or `cython` before install test package)
The active Python binary can be accessed using `python`, and pip with `pip`; `cibuildwheel` makes sure the right version of Python and pip will be executed. `{project}` can be used as a placeholder for the absolute path to the project's root and will be replaced by `cibuildwheel`.
The active Python binary can be accessed using `python`, and pip with `pip`; `cibuildwheel` makes sure the right version of Python and pip will be executed. The placeholder `{package}` can be used here; it will be replaced by the path to the package being built by `cibuildwheel`.
The command is run in a shell, so you can write things like `cmd1 && cmd2`.
@@ -358,13 +426,13 @@ Platform-specific variants also available:<br/>
#### Examples
```yaml
# install test dependencies with overwritten environment variables.
# install test dependencies with overwritten environment variables.
CIBW_BEFORE_TEST: CC=gcc CXX=g++ pip install -r requirements.txt
# chain commands using &&
CIBW_BEFORE_TEST: rm -rf ./data/cache && mkdir -p ./data/cache
# install non pip python package
# install non pip python package
CIBW_BEFORE_TEST: cd some_dir; ./configure; make; make install
# install python packages that are required to install test dependencies
@@ -394,13 +462,16 @@ CIBW_BUILD_VERBOSITY: 1
```text
usage: cibuildwheel [-h] [--platform {auto,linux,macos,windows}]
[--output-dir OUTPUT_DIR] [--print-build-identifiers]
[project_dir]
[package_dir]
Build wheels for all the platforms.
positional arguments:
project_dir Path to the project that you want wheels for.
Default: the current directory.
package_dir Path to the package that you want wheels for. Must be
a subdirectory of the working directory. When set, the
working directory is still considered the 'project'
and is copied into the Docker container on Linux.
Default: the working directory.
optional arguments:
-h, --help show this help message and exit
@@ -411,13 +482,12 @@ optional arguments:
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 Python at C:\PythonXX[-x64].
for all versions of Python. Default: auto.
--output-dir OUTPUT_DIR
Destination folder for the wheels.
--print-build-identifiers
Print the build identifiers matched by the current
invocation and exit.
```
<style>
+1 -1
View File
@@ -12,7 +12,7 @@ stack: python 3.7
init:
- cmd: set PATH=C:\Python37;C:\Python37\Scripts;%PATH%
install: python -m pip install cibuildwheel==1.3.0
install: python -m pip install cibuildwheel==1.4.0
build_script: python -m cibuildwheel --output-dir wheelhouse
+3 -3
View File
@@ -5,7 +5,7 @@ jobs:
- task: UsePythonVersion@0
- bash: |
python3 -m pip install --upgrade pip
pip3 install cibuildwheel==1.3.0
pip3 install cibuildwheel==1.4.0
cibuildwheel --output-dir wheelhouse .
- task: PublishBuildArtifacts@1
inputs: {pathtoPublish: 'wheelhouse'}
@@ -16,7 +16,7 @@ jobs:
- task: UsePythonVersion@0
- bash: |
python3 -m pip install --upgrade pip
pip3 install cibuildwheel==1.3.0
pip3 install cibuildwheel==1.4.0
cibuildwheel --output-dir wheelhouse .
- task: PublishBuildArtifacts@1
inputs: {pathtoPublish: 'wheelhouse'}
@@ -29,7 +29,7 @@ jobs:
displayName: Install Visual C++ for Python 2.7
- bash: |
python -m pip install --upgrade pip
pip install cibuildwheel==1.3.0
pip install cibuildwheel==1.4.0
cibuildwheel --output-dir wheelhouse .
- task: PublishBuildArtifacts@1
inputs: {pathtoPublish: 'wheelhouse'}
+2 -2
View File
@@ -11,7 +11,7 @@ jobs:
- run:
name: Build the Linux wheels.
command: |
pip3 install --user cibuildwheel==1.3.0
pip3 install --user cibuildwheel==1.4.0
cibuildwheel --output-dir wheelhouse
- store_artifacts:
path: wheelhouse/
@@ -25,7 +25,7 @@ jobs:
- run:
name: Build the OS X wheels.
command: |
pip3 install --user cibuildwheel==1.3.0
pip3 install --user cibuildwheel==1.4.0
cibuildwheel --output-dir wheelhouse
- store_artifacts:
path: wheelhouse/
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
- name: Install cibuildwheel
run: |
python -m pip install cibuildwheel==1.3.0
python -m pip install cibuildwheel==1.4.0
- name: Install Visual C++ for Python 2.7
if: startsWith(matrix.os, 'windows')
+1 -1
View File
@@ -25,7 +25,7 @@ env:
# Note: TWINE_PASSWORD is set to a PyPI API token in Travis settings
install:
- python3 -m pip install cibuildwheel==1.3.0
- python3 -m pip install cibuildwheel==1.4.0
script:
# build the wheels, put them into './wheelhouse'
+2 -2
View File
@@ -6,7 +6,7 @@ jobs:
- services: docker
# perform a linux ARMv8 build
- services: docker
arch: aarch64
arch: arm64
# perform a linux PPC64LE build
- services: docker
arch: ppc64le
@@ -26,7 +26,7 @@ jobs:
- ln -s /c/Python38/python.exe /c/Python38/python3.exe
install:
- python3 -m pip install cibuildwheel==1.3.0
- python3 -m pip install cibuildwheel==1.4.0
script:
# build the wheels, put them into './wheelhouse'
+3 -3
View File
@@ -56,7 +56,7 @@ jobs:
- stage: deploy
name: Build and deploy Linux wheels
services: docker
install: python3 -m pip install cibuildwheel==1.3.0
install: python3 -m pip install cibuildwheel==1.4.0
script: python3 -m cibuildwheel --output-dir wheelhouse
after_success: |
python3 -m pip install twine
@@ -66,7 +66,7 @@ jobs:
name: Build and deploy macOS wheels
os: osx
language: shell
install: python3 -m pip install cibuildwheel==1.3.0
install: python3 -m pip install cibuildwheel==1.4.0
script: python3 -m cibuildwheel --output-dir wheelhouse
after_success: |
python3 -m pip install twine
@@ -76,7 +76,7 @@ jobs:
name: Build and deploy Windows wheels
os: windows
language: shell
install: python3 -m pip install cibuildwheel==1.3.0
install: python3 -m pip install cibuildwheel==1.4.0
script: python3 -m cibuildwheel --output-dir wheelhouse
after_success: |
python3 -m pip install twine
+3
View File
@@ -2,3 +2,6 @@
-e ./docs/mkdocs_include_markdown_plugin
pytest
mkdocs==1.0.4
pip-tools
requests
click
+1
View File
@@ -8,5 +8,6 @@ exclude =
.git/,
env/,
env2/,
env??/,
.venv/,
site/
+1 -1
View File
@@ -13,7 +13,7 @@ with io.open(os.path.join(this_directory, 'README.md'), encoding='utf-8') as f:
setup(
name='cibuildwheel',
version='1.3.0',
version='1.4.0',
install_requires=['bashlex!=0.13'],
description="Build Python wheels on CI with minimal configuration.",
long_description=long_description,
@@ -12,7 +12,7 @@ def test(manylinux_image):
if utils.platform != 'linux':
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']:
pytest.skip("manylinux1 and 2010 doesn't exist for non-x86 architectures")
+17 -21
View File
@@ -10,35 +10,30 @@ project_dir = os.path.dirname(__file__)
def test_cpp11(tmp_path):
# This test checks that the C++11 standard is supported
add_env = {'CIBW_SKIP': 'cp27-win* pp27-win32', 'CIBW_ENVIRONMENT': 'STANDARD=11'}
# VC++ for Python 2.7 does not support modern standards
if utils.platform == 'macos':
add_env['MACOSX_DEPLOYMENT_TARGET'] = '10.9'
add_env = {'CIBW_SKIP': 'cp27-win* pp27-win32', 'CIBW_ENVIRONMENT': 'STANDARD=11'}
actual_wheels = utils.cibuildwheel_run(project_dir, add_env=add_env)
expected_wheels = [w for w in utils.expected_wheels(
'spam', '0.1.0', macosx_deployment_target='10.9')
if 'cp27-cp27m-win' not in w
and 'pp27-pypy_73-win32' not in w]
expected_wheels = [w for w in utils.expected_wheels('spam', '0.1.0')
if 'cp27-cp27m-win' not in w and 'pp27-pypy_73-win32' not in w]
assert set(actual_wheels) == set(expected_wheels)
def test_cpp14():
# This test checks that the C++14 standard is supported
add_env = {'CIBW_SKIP': 'cp27-win* pp27-win32 cp35-win*', 'CIBW_ENVIRONMENT': 'STANDARD=14'}
# VC++ for Python 2.7 does not support modern standards
# The manylinux1 docker image does not have a compiler which supports C++11
# Python 3.4 and 3.5 are compiled with MSVC 10, which does not support C++14
if utils.platform == 'macos':
add_env['MACOSX_DEPLOYMENT_TARGET'] = '10.9'
add_env = {'CIBW_SKIP': 'cp27-win* pp27-win32 cp35-win*', 'CIBW_ENVIRONMENT': 'STANDARD=14'}
actual_wheels = utils.cibuildwheel_run(project_dir, add_env=add_env)
expected_wheels = [w for w in utils.expected_wheels(
'spam', '0.1.0', macosx_deployment_target='10.9')
if 'cp27-cp27m-win' not in w
and 'pp27-pypy_73-win32' not in w
and 'cp35-cp35m-win' not in w]
expected_wheels = [w for w in utils.expected_wheels('spam', '0.1.0')
if 'cp27-cp27m-win' not in w
and 'pp27-pypy_73-win32' not in w
and 'cp35-cp35m-win' not in w]
assert set(actual_wheels) == set(expected_wheels)
@@ -52,14 +47,15 @@ def test_cpp17():
pytest.skip('Visual Studio 2015 does not support C++17')
add_env = {'CIBW_SKIP': 'cp27-win* pp27-win32 cp35-win* pp36-win32', 'CIBW_ENVIRONMENT': 'STANDARD=17'}
if utils.platform == 'macos':
add_env['MACOSX_DEPLOYMENT_TARGET'] = '10.13'
actual_wheels = utils.cibuildwheel_run(project_dir, add_env=add_env)
expected_wheels = [w for w in utils.expected_wheels(
'spam', '0.1.0', macosx_deployment_target='10.13')
if 'cp27-cp27m-win' not in w
and 'pp27-pypy_73-win32' not in w
and 'cp35-cp35m-win' not in w
and 'pp36-pypy36_pp73-win32' not in w]
expected_wheels = [w for w in utils.expected_wheels('spam', '0.1.0', macosx_deployment_target='10.13')
if 'cp27-cp27m-win' not in w
and 'pp27-pypy_73-win32' not in w
and 'cp35-cp35m-win' not in w
and 'pp36-pypy36_pp73-win32' not in w]
assert set(actual_wheels) == set(expected_wheels)
@@ -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)
+31
View File
@@ -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",
)
+48
View File
@@ -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)
}
+1
View File
@@ -0,0 +1 @@
print('before_build.py executed!')
@@ -0,0 +1,25 @@
import os
import utils
project_dir = os.path.dirname(__file__)
def test(capfd):
package_dir = os.path.join(project_dir, 'src', 'spam')
# build the wheels
actual_wheels = utils.cibuildwheel_run(project_dir, package_dir=package_dir, add_env={
'CIBW_BEFORE_BUILD': 'python {project}/bin/before_build.py',
'CIBW_TEST_COMMAND': 'python {package}/test/run_tests.py',
# this shouldn't depend on the version of python, so build only
# CPython 3.6
'CIBW_BUILD': 'cp36-*',
})
# check that the expected wheels are produced
expected_wheels = [w for w in utils.expected_wheels('spam', '0.1.0')
if 'cp36' in w]
assert set(actual_wheels) == set(expected_wheels)
captured = capfd.readouterr()
assert "before_build.py executed!" in captured.out
assert "run_tests.py executed!" in captured.out
+8
View File
@@ -0,0 +1,8 @@
from setuptools import Extension, setup
setup(
name="spam",
ext_modules=[Extension('spam', sources=['spam.c'])],
version="0.1.0",
)
+48
View File
@@ -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 @@
print('run_tests.py executed!')
+45 -42
View File
@@ -41,13 +41,15 @@ def cibuildwheel_get_build_identifiers(project_path, env=None):
return cmd_output.strip().split('\n')
def cibuildwheel_run(project_path, env=None, add_env=None, output_dir=None):
def cibuildwheel_run(project_path, package_dir='.', env=None, add_env=None, output_dir=None):
'''
Runs cibuildwheel as a subprocess, building the project at project_path.
Uses the current Python interpreter.
:param project_path: path of the project to be built.
:param package_dir: path of the package to be built. Can be absolute, or
relative to project_path.
:param env: full environment to be used, os.environ if None
:param add_env: environment used to update env
:param output_dir: directory where wheels are saved. If None, a temporary
@@ -64,15 +66,16 @@ def cibuildwheel_run(project_path, env=None, add_env=None, output_dir=None):
with TemporaryDirectoryIfNone(output_dir) as _output_dir:
subprocess.check_call(
[sys.executable, '-m', 'cibuildwheel', '--output-dir', str(_output_dir), project_path],
[sys.executable, '-m', 'cibuildwheel', '--output-dir', str(_output_dir), package_dir],
env=env,
cwd=project_path,
)
wheels = os.listdir(_output_dir)
return wheels
def expected_wheels(package_name, package_version, manylinux_versions=None,
macosx_deployment_target=None):
macosx_deployment_target='10.9'):
'''
Returns a list of expected wheels from a run of cibuildwheel.
'''
@@ -81,60 +84,60 @@ def expected_wheels(package_name, package_version, manylinux_versions=None,
# {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 = ['cp35-cp35m', 'cp36-cp36m', 'cp37-cp37m', 'cp38-cp38']
extra_x86_python_abi_tags = ['cp27-cp27m', 'pp27-pypy_73', 'pp36-pypy36_pp73']
if platform == 'linux':
if pm.machine() not in ['x86_64', 'i686']:
if manylinux_versions is None:
manylinux_versions = ['manylinux2014']
architectures = {'cp': [pm.machine()]}
if manylinux_versions is None:
if pm.machine() == 'x86_64':
manylinux_versions = ['manylinux1', 'manylinux2010']
else:
if manylinux_versions is None:
manylinux_versions = ['manylinux1', 'manylinux2010']
python_abi_tags += extra_x86_python_abi_tags
manylinux_versions = ['manylinux2014']
python_abi_tags = ['cp35-cp35m', 'cp36-cp36m', 'cp37-cp37m', 'cp38-cp38']
if pm.machine() in ['x86_64', 'AMD64', 'x86']:
python_abi_tags += ['cp27-cp27m', 'pp27-pypy_73', 'pp36-pypy36_pp73']
if platform == 'linux':
python_abi_tags.append('cp27-cp27mu') # python 2.7 has 2 different ABI on manylinux
architectures = {'cp': ['x86_64', 'i686'], 'pp': ['x86_64']}
platform_tags = {}
for python_implemention in architectures:
platform_tags[python_implemention] = [
'{manylinux_version}_{architecture}'.format(
manylinux_version=manylinux_version, architecture=architecture)
for architecture in architectures[python_implemention]
wheels = []
for python_abi_tag in python_abi_tags:
platform_tags = []
if platform == 'linux':
architectures = [pm.machine()]
if pm.machine() == 'x86_64' and python_abi_tag.startswith('cp'):
architectures.append('i686')
platform_tags = [
'{}_{}'.format(manylinux_version, architecture)
for architecture in architectures
for manylinux_version in manylinux_versions
]
def get_platform_tags(python_abi_tag):
return platform_tags[python_abi_tag[:2]]
elif platform == 'windows':
python_abi_tags += extra_x86_python_abi_tags
platform_tags = {'cp': ['win32', 'win_amd64'], 'pp': ['win32']}
elif platform == 'windows':
if python_abi_tag.startswith('cp'):
platform_tags = ['win32', 'win_amd64']
else:
platform_tags = ['win32']
def get_platform_tags(python_abi_tag):
return platform_tags[python_abi_tag[:2]]
elif platform == 'macos':
platform_tags = ['macosx_{}_x86_64'.format(macosx_deployment_target.replace('.', '_'))]
elif platform == 'macos':
python_abi_tags += extra_x86_python_abi_tags
else:
raise Exception('unsupported platform')
def get_platform_tags(python_abi_tag):
default_version = '10.7' if python_abi_tag.startswith('pp') else '10.9'
return ['macosx_{}_x86_64'.format((macosx_deployment_target or default_version).replace('.', '_'))]
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(
for platform_tag in platform_tags:
wheels.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 and '-pp2' not in t]
wheels = [w for w in wheels if '-cp27-' not in w and '-pp2' not in w]
return templates
return wheels
platform = None
+30
View File
@@ -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')
)
+4 -4
View File
@@ -18,7 +18,7 @@ class ArgsInterceptor:
self.kwargs = kwargs
MOCK_PROJECT_DIR = 'some_project_dir'
MOCK_PACKAGE_DIR = 'some_package_dir'
@pytest.fixture(autouse=True)
@@ -39,20 +39,20 @@ def mock_protection(monkeypatch):
@pytest.fixture(autouse=True)
def fake_project_dir(monkeypatch):
def fake_package_dir(monkeypatch):
'''
Monkey-patch enough for the main() function to run
'''
real_os_path_exists = os.path.exists
def mock_os_path_exists(path):
if path == os.path.join(MOCK_PROJECT_DIR, 'setup.py'):
if path == os.path.join(MOCK_PACKAGE_DIR, 'setup.py'):
return True
else:
return real_os_path_exists(path)
monkeypatch.setattr(os.path, 'exists', mock_os_path_exists)
monkeypatch.setattr(sys, 'argv', ['cibuildwheel', MOCK_PROJECT_DIR])
monkeypatch.setattr(sys, 'argv', ['cibuildwheel', MOCK_PACKAGE_DIR])
@pytest.fixture(params=['linux', 'macos', 'windows'])
+28 -24
View File
@@ -1,4 +1,5 @@
import sys
from fnmatch import fnmatch
import pytest
@@ -17,13 +18,13 @@ def test_output_dir(platform, intercepted_build_args, monkeypatch):
main()
assert intercepted_build_args.kwargs['output_dir'] == OUTPUT_DIR
assert intercepted_build_args.args[0].output_dir == OUTPUT_DIR
def test_output_dir_default(platform, intercepted_build_args, monkeypatch):
main()
assert intercepted_build_args.kwargs['output_dir'] == 'wheelhouse'
assert intercepted_build_args.args[0].output_dir == 'wheelhouse'
@pytest.mark.parametrize('also_set_environment', [False, True])
@@ -36,7 +37,7 @@ def test_output_dir_argument(also_set_environment, platform, intercepted_build_a
main()
assert intercepted_build_args.kwargs['output_dir'] == OUTPUT_DIR
assert intercepted_build_args.args[0].output_dir == OUTPUT_DIR
def test_build_selector(platform, intercepted_build_args, monkeypatch):
@@ -48,7 +49,7 @@ def test_build_selector(platform, intercepted_build_args, monkeypatch):
main()
intercepted_build_selector = intercepted_build_args.kwargs['build_selector']
intercepted_build_selector = intercepted_build_args.args[0].build_selector
assert isinstance(intercepted_build_selector, BuildSelector)
assert intercepted_build_selector('build-this')
assert not intercepted_build_selector('skip-that')
@@ -57,19 +58,19 @@ def test_build_selector(platform, intercepted_build_args, monkeypatch):
@pytest.mark.parametrize('architecture, image, full_image', [
('x86_64', None, 'quay.io/pypa/manylinux2010_x86_64'),
('x86_64', 'manylinux1', 'quay.io/pypa/manylinux1_x86_64'),
('x86_64', 'manylinux2010', 'quay.io/pypa/manylinux2010_x86_64'),
('x86_64', 'manylinux2014', 'quay.io/pypa/manylinux2014_x86_64'),
('x86_64', None, 'quay.io/pypa/manylinux2010_x86_64:*'),
('x86_64', 'manylinux1', 'quay.io/pypa/manylinux1_x86_64:*'),
('x86_64', 'manylinux2010', 'quay.io/pypa/manylinux2010_x86_64:*'),
('x86_64', 'manylinux2014', 'quay.io/pypa/manylinux2014_x86_64:*'),
('x86_64', 'custom_image', 'custom_image'),
('i686', None, 'quay.io/pypa/manylinux2010_i686'),
('i686', 'manylinux1', 'quay.io/pypa/manylinux1_i686'),
('i686', 'manylinux2010', 'quay.io/pypa/manylinux2010_i686'),
('i686', 'manylinux2014', 'quay.io/pypa/manylinux2014_i686'),
('i686', None, 'quay.io/pypa/manylinux2010_i686:*'),
('i686', 'manylinux1', 'quay.io/pypa/manylinux1_i686:*'),
('i686', 'manylinux2010', 'quay.io/pypa/manylinux2010_i686:*'),
('i686', 'manylinux2014', 'quay.io/pypa/manylinux2014_i686:*'),
('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', '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', 'custom_image', 'custom_image'),
])
@@ -80,9 +81,12 @@ def test_manylinux_images(architecture, image, full_image, platform, intercepted
main()
if platform == 'linux':
assert intercepted_build_args.kwargs['manylinux_images'][architecture] == full_image
assert fnmatch(
intercepted_build_args.args[0].manylinux_images[architecture],
full_image
)
else:
assert 'manylinux_images' not in intercepted_build_args.kwargs
assert intercepted_build_args.args[0].manylinux_images is None
def get_default_repair_command(platform):
@@ -109,7 +113,7 @@ def test_repair_command(repair_command, platform_specific, platform, intercepted
main()
expected_repair = repair_command or get_default_repair_command(platform)
assert intercepted_build_args.kwargs['repair_command'] == expected_repair
assert intercepted_build_args.args[0].repair_command == expected_repair
@pytest.mark.parametrize('environment', [
@@ -128,7 +132,7 @@ def test_environment(environment, platform_specific, platform, intercepted_build
main()
intercepted_environment = intercepted_build_args.kwargs['environment']
intercepted_environment = intercepted_build_args.args[0].environment
assert isinstance(intercepted_environment, ParsedEnvironment)
assert intercepted_environment.as_dictionary(prev_environment={}) == environment
@@ -145,7 +149,7 @@ def test_test_requires(test_requires, platform_specific, platform, intercepted_b
main()
assert intercepted_build_args.kwargs['test_requires'] == (test_requires or '').split()
assert intercepted_build_args.args[0].test_requires == (test_requires or '').split()
@pytest.mark.parametrize('test_extras', [None, 'extras'])
@@ -160,7 +164,7 @@ def test_test_extras(test_extras, platform_specific, platform, intercepted_build
main()
assert intercepted_build_args.kwargs['test_extras'] == ('[' + test_extras + ']' if test_extras else '')
assert intercepted_build_args.args[0].test_extras == ('[' + test_extras + ']' if test_extras else '')
@pytest.mark.parametrize('test_command', [None, 'test --command'])
@@ -175,7 +179,7 @@ def test_test_command(test_command, platform_specific, platform, intercepted_bui
main()
assert intercepted_build_args.kwargs['test_command'] == test_command
assert intercepted_build_args.args[0].test_command == test_command
@pytest.mark.parametrize('before_build', [None, 'before --build'])
@@ -190,7 +194,7 @@ def test_before_build(before_build, platform_specific, platform, intercepted_bui
main()
assert intercepted_build_args.kwargs['before_build'] == before_build
assert intercepted_build_args.args[0].before_build == before_build
@pytest.mark.parametrize('build_verbosity', [None, 0, 2, -2, 4, -4])
@@ -206,7 +210,7 @@ def test_build_verbosity(build_verbosity, platform_specific, platform, intercept
main()
expected_verbosity = max(-3, min(3, int(build_verbosity or 0)))
assert intercepted_build_args.kwargs['build_verbosity'] == expected_verbosity
assert intercepted_build_args.args[0].build_verbosity == expected_verbosity
@pytest.mark.parametrize('option_name', ['CIBW_BUILD', 'CIBW_SKIP'])
@@ -221,7 +225,7 @@ def test_build_selector_migrations(intercepted_build_args, monkeypatch, option_n
main()
intercepted_build_selector = intercepted_build_args.kwargs['build_selector']
intercepted_build_selector = intercepted_build_args.args[0].build_selector
assert isinstance(intercepted_build_selector, BuildSelector)
if option_name == 'CIBW_BUILD':
+5 -3
View File
@@ -4,7 +4,7 @@ import pytest
from cibuildwheel.__main__ import main
from conftest import MOCK_PROJECT_DIR # noqa: I100
from conftest import MOCK_PACKAGE_DIR # noqa: I100
def test_unknown_platform_non_ci(monkeypatch, capsys):
@@ -12,6 +12,7 @@ def test_unknown_platform_non_ci(monkeypatch, capsys):
monkeypatch.delenv('BITRISE_BUILD_NUMBER', raising=False)
monkeypatch.delenv('AZURE_HTTP_USER_AGENT', raising=False)
monkeypatch.delenv('GITHUB_WORKFLOW', raising=False)
monkeypatch.delenv('CIBW_PLATFORM', raising=False)
with pytest.raises(SystemExit) as exit:
main()
@@ -25,6 +26,7 @@ def test_unknown_platform_non_ci(monkeypatch, capsys):
def test_unknown_platform_on_ci(monkeypatch, capsys):
monkeypatch.setenv('CI', 'true')
monkeypatch.setattr(sys, 'platform', 'nonexistent')
monkeypatch.delenv('CIBW_PLATFORM', raising=False)
with pytest.raises(SystemExit) as exit:
main()
@@ -51,10 +53,10 @@ def test_platform_argument(platform, intercepted_build_args, monkeypatch):
main()
assert intercepted_build_args.kwargs['project_dir'] == MOCK_PROJECT_DIR
assert intercepted_build_args.args[0].package_dir == MOCK_PACKAGE_DIR
def test_platform_environment(platform, intercepted_build_args, monkeypatch):
main()
assert intercepted_build_args.kwargs['project_dir'] == MOCK_PROJECT_DIR
assert intercepted_build_args.args[0].package_dir == MOCK_PACKAGE_DIR