Compare commits

...
247 Commits
Author SHA1 Message Date
Joe Rickerby c884530a1d Bump version: v1.5.3 2020-07-19 11:59:56 +01:00
Joe Rickerby acf0320f68 Merge pull request #405 from mayeut/update-cp38
Update CPython 3.8 from 3.8.3 to 3.8.4
2020-07-18 11:52:57 +01:00
mayeut 5368b3bc64 Update CPython 3.8 from 3.8.3 to 3.8.4
Changelog: https://docs.python.org/release/3.8.4/whatsnew/changelog.html#changelog
2020-07-18 11:05:23 +02:00
Joe Rickerby acf60364c0 Merge pull request #403 from joerick/appveyor-debug-env
Debug Appveyor errors
2020-07-17 15:00:04 +01:00
Joe Rickerby 7d983cdf8c Reenable all tests 2020-07-17 09:39:22 +01:00
Joe Rickerby 6847454850 Remove debug code 2020-07-17 09:31:53 +01:00
Joe Rickerby af2a171994 Fix quotes 2020-07-17 09:09:02 +01:00
Joe Rickerby b90c4035b3 Update test_environment.py 2020-07-16 07:56:15 +01:00
Joe Rickerby 56cd6c965d Avoid backslash-in-env-var entirely 2020-07-15 22:02:20 +01:00
Joe Rickerby 5df97a4121 Correct quoting of semicolon 2020-07-15 21:34:30 +01:00
Joe Rickerby 69fe085ba3 more back-slash hilarity 2020-07-15 21:25:29 +01:00
Joe Rickerby 08eddeff8b Fix test and add another more awkward example 2020-07-15 21:07:33 +01:00
Joe Rickerby 9fb748bfb0 Add unit test for this failure mode 2020-07-12 21:45:46 +01:00
Joe Rickerby dc446d3cac moar debug 2020-07-12 12:45:59 +01:00
Joe Rickerby 9f71900637 add debugging code 2020-07-12 12:39:33 +01:00
Joe Rickerby e098e1450d Fix test project generation script 2020-07-12 12:35:30 +01:00
Joe Rickerby ed0077a995 Merge pull request #386 from joerick/docker-exec
From Linux bash script to Python control flow
2020-07-11 19:28:40 +01:00
Joe Rickerby 5842a75927 Flake8 fixes 2020-07-10 13:04:50 +01:00
Joe Rickerby 44e3198880 Convert glob to take a Path and a str pattern 2020-07-10 12:57:05 +01:00
Joe Rickerby 4c0af9f152 Finish file/dir tests 2020-07-10 12:46:32 +01:00
Joe Rickerby 8ddc6d57a2 Bump version: v1.5.2 2020-07-08 22:59:40 +01:00
Joe Rickerby 9e35d65de4 Add file-based unit tests 2020-07-08 22:56:33 +01:00
Joe Rickerby 58fa77b73b Merge pull request #401 from joerick/windows_call
Windows - distinguishing between call and shell for better arg quoting
2020-07-08 22:01:57 +01:00
Joe RickerbyandHenry Schreiner 048ddc4af3 Update test/test_pep518.py
Co-authored-by: Henry Schreiner <HenrySchreinerIII@gmail.com>
2020-07-08 20:12:50 +01:00
Joe Rickerby c521dca40b My own unique attempt on the shell/call debacle 2020-07-08 18:17:55 +01:00
Henry Schreiner ac2b391790 Typo 2020-07-07 23:33:03 -04:00
Henry Schreiner 47fe71efb4 Try just converting to list 2020-07-07 23:09:40 -04:00
Henry Schreiner 53ba9bd0d0 Try adding PATH to env 2020-07-06 18:11:29 -04:00
Henry Schreiner 317397b39a fix: Try resolving 2020-07-06 14:25:02 -04:00
Henry Schreiner 8466194576 fix: Try removing shell 2020-07-06 10:49:24 -04:00
Henry Schreiner c68be58e78 fix: Try simple quoting 2020-07-06 08:55:36 -04:00
Henry Schreiner fc24230cb4 Try original 2020-07-06 01:31:47 -04:00
Henry Schreiner c36be04ee8 fix: Use repr 2020-07-05 23:51:28 -04:00
Henry Schreiner 7a27ae941c fix: Support environment markers in PEP 518 workaround 2020-07-05 23:16:00 -04:00
Joe Rickerby 833a069207 Pass PATH during command substitution test 2020-07-05 17:39:40 +01:00
Joe Rickerby cc913ee7cf Use a different method for the Windows test 2020-07-05 17:27:29 +01:00
Joe Rickerby d0b6401129 Merge pull request #394 from mayeut/update-cpython-3.7
Update CPython 3.7 from 3.7.7 to 3.7.8
2020-07-05 14:50:00 +01:00
mayeut 141b3e031e Update CPython 3.7 from 3.7.7 to 3.7.8
Changelog: https://docs.python.org/release/3.7.8/whatsnew/changelog.html#changelog
2020-07-04 18:11:06 +02:00
Yannick Jadoul 0beb9518c9 Merge pull request #393 from Czaki/fix-doc1
Update links in documentation
2020-07-04 16:37:53 +02:00
Grzegorz Bokota ad51e796c0 Update links in documentation
fix #391
2020-07-04 12:02:16 +02:00
Joe Rickerby b12a560e87 Add docker test images for other architectures 2020-07-01 13:22:24 +01:00
Joe Rickerby e31c24d3bb Fix overridden PATH test, by ensuring bash command substitution rules 2020-06-28 21:51:06 +01:00
Joe Rickerby 27d97daf13 Don't use redirection syntax in Windows environment. Fix absolute path use for Mac 2020-06-28 11:33:22 +01:00
Joe Rickerby faf6a76375 Change 'slow' tests to 'docker' tests, only run them on Linux 2020-06-28 10:53:03 +01:00
Joe Rickerby 91c3e90017 Fix Python 3.6 compatibility 2020-06-26 21:47:42 +01:00
Joe Rickerby ad871f886a Fix style 2020-06-26 21:41:00 +01:00
Joe Rickerby c956f118f6 Add unit tests for DockerContainer 2020-06-26 21:37:02 +01:00
Joe Rickerby 762dc807cd Convert DockerContainer to use binary PIPEs, to handle arbitrary data 2020-06-26 21:36:52 +01:00
Joe Rickerby 2edb33662b Import organisation 2020-06-26 20:22:49 +01:00
Joe Rickerby df69dda44a Fix broken PATH test - it was erroring for a different reason 2020-06-26 12:59:26 +01:00
Joe Rickerby 1d9333689f Explicit buffer size isn't necessary 2020-06-26 12:55:38 +01:00
Joe Rickerby 68d8f89fb3 Style tweaks 2020-06-26 11:02:23 +01:00
Joe Rickerby db9d658b51 Merge pull request #387 from joerick/call-types
Remove str() calls in call/shell functions using destructuring syntax
2020-06-26 00:20:22 +01:00
Joe Rickerby 1d88c27530 Neaten function call whitespace 2020-06-25 12:41:21 +01:00
Joe Rickerby fefe128a15 Fix issues with spaces in environment variables 2020-06-25 11:21:10 +01:00
Joe Rickerby a92959f191 Update README.md 2020-06-25 10:57:58 +01:00
Joe Rickerby 321335e756 Bump version: v1.5.1 2020-06-25 10:53:30 +01:00
Joe Rickerby c88a73b40b Merge pull request #389 from Czaki/fix_shutil
Switch back to shutil.move from Path.rename
2020-06-25 10:44:53 +01:00
Joe Rickerby dd4fc69e7c Revert "Remove hard-coded shell info to use the docker ENTRYPOINT instead"
This reverts commit a052c71aec.
2020-06-25 10:39:26 +01:00
Grzegorz Bokota 4dd5c0e11c use only dir name as target of shutil.move 2020-06-24 23:04:15 +00:00
Grzegorz Bokota a2f4d2aa47 Switch back to shutil.move from Path.rename 2020-06-24 22:36:18 +00:00
Joe Rickerby 850d109582 Don't use docker exec -w because CircleCI doesn't support it 2020-06-24 17:12:06 +01:00
Joe Rickerby a052c71aec Remove hard-coded shell info to use the docker ENTRYPOINT instead
This lets us use the 'linux32 bash' that's specified by the i686 docker
images, without specifying it explicitly.
2020-06-24 16:50:08 +01:00
Joe Rickerby 31339ba30e Extract DockerContainer into its own file 2020-06-24 16:43:33 +01:00
Joe Rickerby 53c00a0af6 Use a long-running remote bash shell, rather than docker exec 2020-06-24 16:39:20 +01:00
Joe Rickerby 41891277d3 Bump version: v1.5.0 2020-06-24 09:54:55 +01:00
Joe Rickerby 78ac83ff7e Merge pull request #358 from henryiii/henryiii/pep517
Adding PEP 518 test, fix 3.5 Python on Windows
2020-06-24 09:34:25 +01:00
Joe Rickerby 50a263df21 Remove str() calls in call/shell functions using destructuring syntax 2020-06-23 20:28:54 +01:00
Joe Rickerby e417791ae4 Remove the linux bash script, replaced with a docker exec approach 2020-06-23 19:46:23 +01:00
Joe Rickerby 3ff84c52f9 Merge branch 'master' into henryiii/pep517 2020-06-23 19:39:59 +01:00
probot-auto-merge[bot] e72e4683db Merge pull request #383 from joerick/circleci-debug
Debug CircleCI failures
2020-06-23 11:26:55 +00:00
Joe Rickerby dfd8f4809a Merge remote-tracking branch 'origin/master' into circleci-debug 2020-06-23 11:17:36 +01:00
Joe Rickerby 666699a51e Configure probot-auto-merge 2020-06-23 11:16:23 +01:00
Joe Rickerby 947059bbf4 Don't try to install virtualenv 2020-06-23 10:27:23 +01:00
Joe Rickerby 4302090f08 Uninstall poetry 2020-06-23 10:25:35 +01:00
Joe Rickerby c68d50a073 Lock virtualenv version 2020-06-23 10:19:09 +01:00
Joe Rickerby a03a26c90e Update README options table 2020-06-23 09:59:22 +01:00
Joe Rickerby e3b272201e Merge pull request #342 from Czaki/setup_data
Add BEFORE_ALL options
2020-06-23 09:51:35 +01:00
Grzegorz Bokota 43fcb31231 fix tests 2020-06-22 13:55:04 +02:00
Grzegorz Bokota b4b46a1f06 fix mypy 2020-06-22 13:55:04 +02:00
Joe Rickerby 2057bba07f Update docs to reflect linux available python change 2020-06-22 13:55:04 +02:00
Joe Rickerby 46352c8e30 Add a modern python to linux build before_all environment 2020-06-22 13:55:04 +02:00
Grzegorz Bokota 2e15fdddfd change to new test engine 2020-06-22 13:55:04 +02:00
Grzegorz Bokota fc52c5e7b6 add unit_test 2020-06-22 13:55:04 +02:00
Grzegorz Bokota 7b7e24d36d add missed quote of before_all command 2020-06-22 13:55:04 +02:00
Grzegorz Bokota 9f6c91404d fix flake8 2020-06-22 13:55:04 +02:00
Grzegorz BokotaandJoe Rickerby 219a65d5e3 Apply suggestions from code review
Co-authored-by: Joe Rickerby <joerick@mac.com>
2020-06-22 13:55:04 +02:00
Grzegorz Bokota 36b8f7fff2 changes in code from code review 2020-06-22 13:55:04 +02:00
Grzegorz Bokota 4d5cf4629e fix mypy type warning 2020-06-22 13:55:04 +02:00
Grzegorz Bokota c1f155c0f2 add environment evaluation 2020-06-22 13:55:04 +02:00
Grzegorz Bokota 3fe560cf14 fix mypy 2020-06-22 13:55:04 +02:00
Grzegorz Bokota 75a0384d02 fix flake8 errors 2020-06-22 13:55:04 +02:00
Grzegorz Bokota 6501470357 Add BEFORE_ALL options 2020-06-22 13:55:04 +02:00
Joe Rickerby 8bfc313d02 Merge pull request #347 from Czaki/pre_commit
Pre commit
2020-06-22 12:19:11 +01:00
Joe Rickerby e6ac527416 Convert the workaround to use Path objects 2020-06-22 12:08:40 +01:00
Joe Rickerby c27c5feadb Merge branch 'master' into henryiii/pep517 2020-06-22 12:06:32 +01:00
Joe Rickerby 2b22ffda9b Add python3.9 note to version matrix 2020-06-22 11:42:41 +01:00
Yannick Jadoul 9a1f8a99e0 Merge pull request #376 from YannickJadoul/pathlib
Use pathlib.Path
2020-06-22 12:27:20 +02:00
Yannick Jadoul d552d29eae Revert use of Path.joinpath instead of Path.__div__ 2020-06-21 13:14:53 +02:00
Grzegorz Bokota aa99405a9c unpin flake, exclude pinned_docker_images.cfg 2020-06-19 21:15:33 +02:00
Grzegorz Bokota 39101ae8ea update mypy config 2020-06-19 21:12:12 +02:00
Grzegorz Bokota 1a83967261 remove json check 2020-06-19 21:12:12 +02:00
Grzegorz Bokota d43817b49a fixed files 2020-06-19 21:12:12 +02:00
Grzegorz Bokota 3b1f0dd453 add pre-commit configuration 2020-06-19 21:12:12 +02:00
Joe Rickerby a1db0f7ebb Merge pull request #378 from YannickJadoul/ignore-TestProject
Suppress warnings about pytest trying to collect TestProject
2020-06-19 11:02:52 +01:00
Yannick Jadoul acf5c24881 Use patlib.Path in tests and tools 2020-06-18 15:29:48 +02:00
Yannick Jadoul f012fa1af0 Mock Path.mkdir calls in unit tests 2020-06-18 00:23:25 +02:00
Yannick Jadoul b62b17a223 Replace shutil.move with Path.rename or Path.replace 2020-06-18 00:23:25 +02:00
Yannick Jadoul 265708854a Attend to review remarks 2020-06-18 00:23:25 +02:00
Yannick Jadoul e58aed1731 Fix overlooked os.path.abspath 2020-06-18 00:23:25 +02:00
Yannick Jadoul b3ffb04a2c Fix order of pathlib.Path.symlink_to arguments in windows.py 2020-06-18 00:23:25 +02:00
Yannick Jadoul c53e7824a7 Remove unused imports 2020-06-18 00:23:25 +02:00
Yannick Jadoul 46a39b06cb Fix paths in unit tests 2020-06-18 00:23:25 +02:00
Yannick Jadoul a7f73f1593 Use pathlib.Path in main cibuildwheel files 2020-06-18 00:23:25 +02:00
Yannick Jadoul 47323761dd Suppress warnings about pytest trying to collect TestProject 2020-06-18 00:23:09 +02:00
Yannick Jadoul 43fbcfe008 Merge pull request #379 from YannickJadoul/fix-travis-ci-windows-root-ca
Fix SSL error on Windows on Travis CI
2020-06-18 00:21:33 +02:00
Yannick Jadoul 5c19ff3f95 Fix 'ssl.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:852)' error on Windows on Travis CI 2020-06-17 23:26:39 +02:00
Joe Rickerby 02e9ee3e96 Merge pull request #369 from Czaki/ensure_dir_name
Ensure dir name
2020-06-16 15:07:58 +01:00
Joe Rickerby df07aafae1 Merge pull request #374 from YannickJadoul/detect-virtualenv-real_prefix
Simplify virtualenv test in test_testing
2020-06-16 15:06:00 +01:00
Joe Rickerby 78edb0a06a Merge pull request #375 from joerick/appveyor-macos-allow-failures
Allow failures on Appveyor macos
2020-06-16 15:03:14 +01:00
Henry Schreiner 901ac79dbc review: Address review suggestion 2020-06-14 11:25:14 -04:00
Henry Schreiner 0597088f64 Adding PEP 518 test 2020-06-14 09:57:58 -04:00
Joe Rickerby f1c4ebbed0 Allow failures on Appveyor macos
Temporarily allow this build to fail to get the tests green again
2020-06-14 11:03:38 +01:00
Joe Rickerby fd9e3202a0 Merge pull request #348 from YannickJadoul/before-test-docs-order
Making order of options more consistent in docs and BuildOptions fields
2020-06-14 10:55:11 +01:00
Joe Rickerby 7afdd40714 Merge pull request #354 from henryiii/patch-2
Better help for GitHub Actions + Py27 workaround
2020-06-14 09:56:22 +01:00
Yannick Jadoul ed491d0e22 Simplify virtualenv test in test_testing 2020-06-14 00:37:34 +02:00
Joe Rickerby e12ca14006 Merge pull request #362 from breznak/gh-action-pypi-publish
example: for GH Actions use action to publish
2020-06-10 13:20:16 +01:00
Joe Rickerby 58fc418563 Update to add clarity to on: section 2020-06-10 13:18:22 +01:00
Yannick Jadoul 57e6f0f56d Replace "startsWith(matrix.os, 'windows')" by "runner.os == 'Windows'" in GitHub Actions configurations 2020-06-10 01:53:41 +02:00
Yannick Jadoul cc8dbbe8b3 Make deploy on published GitHub release optional in examples/github-deploy.yml 2020-06-08 15:50:38 +02:00
Yannick Jadoul bb5aac3d69 Move if-clause of PyPI upload to job level rather than action level, and specify to use only published releases 2020-06-08 13:23:28 +02:00
Grzegorz Bokota 374e88382b move abspath to system specific files 2020-06-08 10:19:02 +02:00
Grzegorz Bokota c74acd6363 change to abspath 2020-06-08 08:05:25 +02:00
Grzegorz Bokota 90023a3a70 fix logic for test 2020-06-06 20:26:50 +02:00
Grzegorz Bokota 0b82ab6674 ensure that package_dir is provided as package 2020-06-06 20:05:09 +02:00
Henry Schreiner fb0e192681 Add example snippit 2020-06-05 16:58:57 -04:00
Yannick Jadoul eedc5c6b79 Remove twine check from examples/github-deploy.yml as it's now integrated into the pypa/gh-action-pypi-publish action 2020-06-03 19:01:01 +02:00
Joe Rickerby 31c9247921 Merge pull request #360 from heitorlessa/improv/support-pyproject-poetry
Support projects without setup.py (pyproject.toml and setup.cfg are also valid)
2020-06-02 21:30:48 +01:00
Yannick JadoulandJoe Rickerby 4c3cbf702c Update examples/github-deploy.yml to upload on tags, as alternative to GitHub releases
Co-authored-by: Joe Rickerby <joerick@mac.com>
2020-06-02 22:09:33 +02:00
Yannick Jadoul a06998e2f6 Merge pull request #366 from YannickJadoul/ignore-docker-rm
Ignore errors when cleaning up docker container in linux.py
2020-06-02 21:45:50 +02:00
Joe Rickerby 4e15e202c8 style- inline function 2020-06-02 20:03:04 +01:00
Joe Rickerby 35ea7b6307 Remove debugging flags 2020-06-02 20:02:54 +01:00
Yannick Jadoul 46688ae3f9 Revert previous solution and just don't try removing the container if 'docker create' errors 2020-06-02 17:15:27 +02:00
Yannick Jadoul 9c3f44d086 Rename examples/*-deploy-only.yml to examples/*-deploy.yml, and keep sdist in dist/ in examples/github-deploy.yml 2020-06-02 17:07:18 +02:00
Yannick Jadoul 896337cbb1 Updating all GitHub actions version to actions/checkout@v2, actions/setup-python@v2, and actions/upload-artifact@v2 2020-06-02 15:23:15 +02:00
Yannick Jadoul a0dab888a7 Ignore errors when cleaning up docker container in linux.py 2020-06-02 15:09:41 +02:00
Yannick Jadoul d7cffde00f Add 'release' to 'on' section in examples/github-deploy-only.yml 2020-06-02 15:07:06 +02:00
Yannick Jadoul 562d81c6ea Add links to the PyPI deployment examples to the docs 2020-06-02 12:03:06 +02:00
Yannick Jadoul 66b392f4ee Implement @henriii's suggestion, adding a separate job on Linux to upload all artifacts at once 2020-06-02 11:48:15 +02:00
Marek Otahal dd3c87ece2 example: small doc 2020-06-01 16:04:44 +02:00
Marek Otahal 63f7a2ddc4 examples: revert to minima GH example, add deploymet
example showing pypi and GH release
2020-06-01 16:01:29 +02:00
Marek Otahal ca49d86ed8 example: publish to PyPI only on release tag 2020-06-01 15:32:51 +02:00
heitorlessa d9a317c370 fix: address Yannick's PR feedback 2020-06-01 14:30:14 +01:00
Marek Otahal add792de30 example: for GH Actions use action to publish
to PyPI, instead of calling twine directly.
2020-06-01 15:02:26 +02:00
heitorlessa 004fc03899 fix: remove integ test 2020-06-01 13:21:52 +01:00
heitorlessa 15f1678439 fix: install poetry before build for windows
Signed-off-by: heitorlessa <lessa@amazon.co.uk>
2020-06-01 10:44:58 +01:00
heitorlessa 3de38fc679 fix: assert single wheel workaround
Signed-off-by: heitorlessa <lessa@amazon.co.uk>
2020-06-01 10:03:53 +01:00
heitorlessa 63eda18cf9 chore: increase test verbosity to capture errors
Signed-off-by: heitorlessa <lessa@amazon.co.uk>
2020-06-01 09:31:54 +01:00
heitorlessa a15a5f1c0f fix: exclude pypy build that's failing
Signed-off-by: heitorlessa <lessa@amazon.co.uk>
2020-06-01 08:58:24 +01:00
heitorlessa d41cf65173 improv: address Henrii's feedback
Signed-off-by: heitorlessa <lessa@amazon.co.uk>
2020-06-01 08:37:08 +01:00
Yannick Jadoul e97b103148 Group environment variables querying in __main__.py 2020-06-01 00:04:32 +02:00
heitorlessa 4e6246d2a7 chore: flake8
Signed-off-by: heitorlessa <lessa@amazon.co.uk>
2020-05-31 22:43:30 +01:00
heitorlessa ab4f5209b5 improv: document test given when then
Signed-off-by: heitorlessa <lessa@amazon.co.uk>
2020-05-31 22:39:19 +01:00
heitorlessa 8a25718c71 fix: ignores images with outdated pip version
Signed-off-by: heitorlessa <lessa@amazon.co.uk>
2020-05-31 22:37:52 +01:00
heitorlessa a8bdac07e6 fix: python version requirement range
Signed-off-by: heitorlessa <lessa@amazon.co.uk>
2020-05-31 22:02:27 +01:00
Yannick Jadoul 2790fd1dd7 Put CIBW_TEST_COMMAND as first test option everywhere 2020-05-31 21:42:21 +02:00
heitorlessa f9401d94c2 fix: support py2.7 to accommodate current setup
Signed-off-by: heitorlessa <lessa@amazon.co.uk>
2020-05-31 20:14:24 +01:00
heitorlessa 4fb7874ca3 chore: address PR feedback
Signed-off-by: heitorlessa <lessa@amazon.co.uk>
2020-05-31 20:13:52 +01:00
heitorlessa bb1b050ab5 improv: add integ test dummy poetry package 2020-05-31 19:55:27 +01:00
heitorlessa e8d857db21 chore: flake8 mypy linting
Signed-off-by: heitorlessa <lessa@amazon.co.uk>
2020-05-31 17:56:43 +01:00
heitorlessa f3a136be78 improv: support pyproject.toml existence
Signed-off-by: heitorlessa <lessa@amazon.co.uk>
2020-05-31 17:36:30 +01:00
Henry Schreiner 2c564dfb9c Mention action, drop example code. 2020-05-27 19:45:34 -04:00
Henry Schreiner 901f708d4c Better help for GitHub Actions + Py27 workaround 2020-05-27 15:56:12 -04:00
Joe Rickerby ecbdc2e3d3 Bump version: v1.4.2 2020-05-25 16:16:13 +01:00
Joe Rickerby 60a3d51a50 Merge pull request #352 from mayeut/isolation-test
Add test case for proper build isolation
2020-05-25 16:07:26 +01:00
mayeut bdc86ba539 Update dependencies 2020-05-25 15:20:56 +02:00
mayeut 04c1a2aee5 Add test case for proper build isolation
If there's not a proper build isolation then,
the wheel for `cp27mu` will pick up the `spam.so` built
for `cp27m` resulting in an import error.
2020-05-25 14:57:58 +02:00
Joe Rickerby e76ebbcdec Merge pull request #334 from joerick/template-tests
Refactor tests to generate projects from template
2020-05-18 09:06:39 +01:00
Yannick Jadoul edd461ad42 Making order of options more consistent in docs and BuildOptions fields 2020-05-17 22:30:18 +02:00
Joe Rickerby 0c323cfb5b Add non-strict typechecking to test module
'strict' is a global flag, so I've had to break out the type-
checking options to be more specific
2020-05-17 20:17:34 +01:00
Joe Rickerby 2e41a4f94a Merge commit 'cf60df549183e88cf84bca9d45550caf62c8be9e' into template-tests 2020-05-17 16:59:06 +01:00
Joe Rickerby 81f960b17e Rename TemplateProject -> TestProject & tidy some comments 2020-05-17 16:57:11 +01:00
Joe Rickerby cf60df5491 Merge pull request #346 from mayeut/update-constraints
Update the versions of our dependencies.
2020-05-17 15:04:52 +01:00
mayeut fd8507212c Update the versions of our dependencies. 2020-05-16 11:19:09 +02:00
Joe Rickerby 420995f567 check_call does require paths as strings on CPython 3.6 Windows 2020-05-15 15:18:17 +01:00
Joe Rickerby 29084bd5bf Merge pull request #345 from mayeut/update-python
Update CPython 3.8 from 3.8.2 to 3.8.3
2020-05-15 15:09:22 +01:00
Joe Rickerby 13907188a1 Fix flake8 errors 2020-05-15 12:35:14 +01:00
mayeut d606aaa85e Update CPython 3.8 from 3.8.2 to 3.8.3
Changelog:
https://github.com/python/cpython/blob/v3.8.3/Misc/NEWS.d/3.8.3.rst
2020-05-15 13:35:05 +02:00
Joe Rickerby 2e8fe841ec Remove TemplateProject subclassing 2020-05-15 12:28:34 +01:00
Joe Rickerby 0411fcc2ae As discussed in code review 2020-05-15 12:19:38 +01:00
Joe Rickerby 6a9ab683a6 Merge pull request #344 from jbarlow83/patch-2
README: add pikepdf
2020-05-15 10:31:57 +01:00
jbarlow83 e9592b2d53 README: add pikepdf 2020-05-14 02:18:42 -07:00
Joe Rickerby db6b1f3541 Merge pull request #341 from mayeut/f-strings
Use f-strings
2020-05-10 08:31:40 +01:00
mayeut 5e767fcc9a Use f-strings
Now that minimum python supported is 3.6, we can use f-strings !
2020-05-09 11:53:20 +02:00
Yannick Jadoul ac0012e3da Merge pull request #315 from YannickJadoul/type-annotations
Add type annotations and drop Python 3.5 host support
2020-05-09 02:37:58 +02:00
Joe Rickerby 71150a1cb5 Remove pypy from the c++17 test 2020-05-08 13:36:53 +01:00
Yannick Jadoul 0368d58065 Run mypy in CircleCI job 2020-05-07 00:41:16 +02:00
Yannick Jadoul 9daad5c60c Ignore .mypy_cache and add mypy configuration to setup.cfg 2020-05-07 00:28:57 +02:00
Yannick Jadoul 4683ac3d6c Remove macOS Python 3.7 job on CircleCI 2020-05-07 00:28:53 +02:00
Yannick Jadoul 7cc85c2e4f Replace Callable[[str], bool] by BuildSelector 2020-05-07 00:07:14 +02:00
Yannick Jadoul 891e2dd6aa Fixing some review remarks 2020-05-06 23:57:33 +02:00
Yannick Jadoul 575e229514 Drop 3.5 host support and adapt CI configurations 2020-05-06 23:57:00 +02:00
Yannick Jadoul 1a765ac431 Use variable annotations instead of workarounds 2020-05-06 23:55:54 +02:00
Yannick Jadoul 5bdb21b705 More type annotations after rebasing 2020-05-06 23:55:54 +02:00
Yannick Jadoul 40967a6572 collections.namedtuple to typing.NameTuple and flake8 fixes 2020-05-06 23:55:54 +02:00
Yannick Jadoul c128018590 Clean up bashlex_eval.py's types 2020-05-06 23:55:54 +02:00
Yannick Jadoul 618f1cefae Added majority of type annotations (to a by now outdated version) 2020-05-06 23:55:54 +02:00
Joe Rickerby a679a09212 Add missing 3.6 pin test 2020-05-06 20:24:13 +01:00
Joe Rickerby cc1ae4a963 Add utility function to checkout a generated project 2020-05-06 20:14:03 +01:00
Joe Rickerby dbd7006704 Remove dead code from merge 2020-05-06 19:51:58 +01:00
Joe Rickerby da4bc0225c Fix new cpp17 test 2020-05-05 21:56:34 +01:00
Joe Rickerby f145c7c148 Merge commit '79278364898eccc08d6bbe6e956b6774bb332c24' into template-tests
# Conflicts:
#	CI.md
#	test/02_test/test/spam_test.py
#	test/10_cpp_standards/cibuildwheel_test.py
2020-05-05 21:52:37 +01:00
Joe Rickerby 73425d4bb8 Fix indent 2020-05-05 21:32:57 +01:00
Joe Rickerby 7927836489 Merge pull request #339 from jlaine/mention-projects
Add PyAV, aiortc and aioquic to example projects
2020-05-04 22:54:29 +01:00
Yannick Jadoul 185a1c09ad Update docs on workaround for modern MSVC/C++ in Python 2.7 2020-05-04 23:39:59 +02:00
Yannick Jadoul 3d4a6b0765 Only build Python 2.7 wheel in test_cpp17_py27_modern_msvc_workaround 2020-05-04 23:39:59 +02:00
Yannick Jadoul 7eb82bf90c Add note in CI.md about reduced tests on AppVeyor 2020-05-04 23:39:59 +02:00
Yannick Jadoul 5986c91937 Also try to build C++14/C++17 wheels for PyPy 3.6 with default MSVC 2020-05-04 23:39:59 +02:00
Yannick Jadoul 63421d40f0 Testing if cp35 works for modern C++ 2020-05-04 23:39:59 +02:00
Yannick Jadoul e11c0c2dd1 Simplify and clarify test_cpp17_modern_msvc_workaround 2020-05-04 23:39:59 +02:00
Yannick Jadoul 362b4314cb Make distinction between 32- and 64-bit Python wheels 2020-05-04 23:39:59 +02:00
Yannick Jadoul ba60f1865f Use setuptools backported versions of distutils._msvccompiler in setuptools.msvc to get environment variables from vcvarsall 2020-05-04 23:39:59 +02:00
Yannick Jadoul f0e65d9f5a Correct VC++ version in test_cpp17_modern_msvc_workaround 2020-05-04 23:39:59 +02:00
Yannick Jadoul e1881976e8 Add test to build wheels on Windows for C++17, setting DISTUTILS_USE_SDK and MSSdk to not use vcpython27 or MSVC 10 2020-05-04 23:39:59 +02:00
Jeremy Lainé 953839be59 Add PyAV, aiortc and aioquic to example projects 2020-05-04 20:20:50 +02:00
Joe Rickerby aaf1bba1cf Merge pull request #337 from Czaki/remove_pypy_patch
clean after bump to pypy 7.3.1
2020-05-04 18:43:30 +01:00
Grzegorz Bokota 196a0323a4 clean after bump to pypy 7.3.1 2020-05-04 10:52:41 +02:00
Joe Rickerby 729bab42c8 Pytest config header 2020-05-03 17:03:35 +01:00
Joe Rickerby a9c475a910 Fix pathlib error on Windows 2020-05-03 16:53:15 +01:00
Joe Rickerby 566a880099 Fix warning 2020-05-03 16:52:35 +01:00
Joe Rickerby bf882b065d Output test timings 2020-05-03 14:18:57 +01:00
Joe Rickerby 1b38974c60 Fix subdir test 2020-05-03 14:09:47 +01:00
Joe Rickerby c568f7ba89 Add pytest-azurepipelines plugin 2020-05-03 14:09:35 +01:00
Joe Rickerby e9803e8fb3 Fix missing dir 2020-05-03 14:09:10 +01:00
Joe Rickerby dafe5cd1f7 Refactor to use tmp_path and pathlib paths 2020-05-03 14:08:57 +01:00
Joe Rickerby 33f210b85c Remove 'setuptools' template project 2020-05-03 10:37:41 +01:00
Yannick Jadoul e2cafa487b Cherry pick: Drop 3.5 host support and adapt CI configurations 2020-05-03 10:37:09 +01:00
Joe Rickerby ffd5d44cdc Fix C++ tests 2020-05-03 10:30:44 +01:00
Joe Rickerby 5d665aac39 Fix up test runner scripts 2020-05-02 22:57:03 +01:00
Joe Rickerby 662612685c Rename tests to remove the number, but keep 'basic' as the first 2020-05-02 22:53:58 +01:00
Joe Rickerby a204df819b Add .mypy_cache to gitignore 2020-05-02 22:51:40 +01:00
Joe Rickerby 9bb075aa0b Fix flake8 errors 2020-05-02 22:50:52 +01:00
Joe Rickerby 007be7aea7 Ensure tests are up-to-date with master 2020-05-02 22:42:35 +01:00
Joe Rickerby 3b35882f87 Complete migration to new style. remove old reference tests 2020-05-02 21:00:58 +01:00
Joe Rickerby 616e9dd6eb Converting tests to the new-style 2020-05-02 16:54:19 +01:00
Joe Rickerby c38bc9064a Reintroduce jinja2 2020-05-02 09:59:55 +01:00
Joe Rickerby 4f75eaabcd Inprogress refactor trying f-strings as template language 2020-04-25 16:09:11 +01:00
116 changed files with 2499 additions and 2087 deletions
+9 -23
View File
@@ -1,18 +1,21 @@
version: 2
jobs:
flake8:
flake8-mypy:
docker:
- image: circleci/python:3.6
steps:
- checkout
- run:
name: Install flake8
command: sudo python -m pip install flake8
name: Install flake8 & mypy
command: sudo python -m pip install flake8 mypy
- run:
name: Test.
command: flake8 .
name: flake8
command: flake8
- run:
name: mypy
command: mypy
osx-python3.6:
macos:
@@ -30,22 +33,6 @@ jobs:
command: venv/bin/python ./bin/run_tests.py
no_output_timeout: 30m
osx-python3.7:
macos:
xcode: "10.0.0"
environment:
PYTHON: python3
steps:
- checkout
- run:
name: Prepare the environment.
command: bash .circleci/prepare.sh
- run:
name: Test.
command: venv/bin/python ./bin/run_tests.py
no_output_timeout: 30m
linux-python3.6:
docker:
- image: circleci/python:3.6
@@ -67,7 +54,6 @@ workflows:
version: 2
all-tests:
jobs:
- flake8
- flake8-mypy
- osx-python3.6
- osx-python3.7
- linux-python3.6
+3 -1
View File
@@ -1,6 +1,8 @@
set -o errexit
set -o xtrace
$PYTHON --version
$PYTHON -m pip --version
$PYTHON -m pip install -q --user --ignore-installed --upgrade virtualenv
$PYTHON -m virtualenv -p $PYTHON venv
venv/bin/python -m pip install -r requirements-dev.txt
venv/bin/python -m pip freeze
+7
View File
@@ -0,0 +1,7 @@
# config file for https://github.com/bobvanderlinden/probot-auto-merge
# no approvals required, just the label is enough
minApprovals: {}
requiredLabels:
- merge when green
+3 -3
View File
@@ -18,8 +18,8 @@ jobs:
os: [ubuntu-18.04, windows-latest, macos-latest]
python_version: ['3.7']
steps:
- uses: actions/checkout@v1
- uses: actions/setup-python@v1
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
name: Install Python ${{ matrix.python_version }}
with:
python-version: ${{ matrix.python_version }}
@@ -28,7 +28,7 @@ jobs:
run: |
python -m pip install -r requirements-dev.txt
- name: Install Visual C++ for Python 2.7
if: startsWith(matrix.os, 'windows')
if: runner.os == 'Windows'
run: |
choco install vcpython27 -f -y
- name: Test cibuildwheel
+3
View File
@@ -102,3 +102,6 @@ env3?/
# VSCode project settings
/.vscode
# MyPy cache
.mypy_cache/
+21
View File
@@ -0,0 +1,21 @@
repos:
- repo: https://gitlab.com/pycqa/flake8
rev: 3.8.1
hooks:
- id: flake8
pass_filenames: true
# this seems to need to be here in addition to setup.cfg
exclude: cibuildwheel/resources/
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.3.0
hooks:
- id: check-yaml
- id: check-merge-conflict
- id: end-of-file-fixer
exclude: cibuildwheel/resources/pinned_docker_images.cfg
- repo: https://github.com/pre-commit/mirrors-mypy
rev: '' # Use the sha / tag you want to point at
hooks:
- id: mypy
files: cibuildwheel|test
exclude: cibuildwheel/resources
+15 -13
View File
@@ -6,22 +6,22 @@ branches:
jobs:
include:
- name: Linux | x86_64 + i686 | Python 3.5
- name: Linux | x86_64 + i686 | Python 3.6
language: python
python: 3.5
python: 3.6
services: docker
env: PYTHON=python
- name: Linux | arm64 | Python 3.5
- name: Linux | arm64 | Python 3.6
language: python
python: 3.5
python: 3.6
services: docker
arch: arm64
env: PYTHON=python
- name: Linux | ppc64le | Python 3.5
- name: Linux | ppc64le | Python 3.6
language: python
python: 3.5
python: 3.6
services: docker
arch: ppc64le
env: PYTHON=python
@@ -30,25 +30,27 @@ jobs:
os: osx
env: PYTHON=python3
- name: Windows | x86_64 | Python 3.5
- name: Windows | x86_64 | Python 3.6
os: windows
language: shell
before_install:
- choco install python3 --version 3.5.4 --no-progress -y
- choco install python3 --version 3.6.8 --no-progress -y
# Update root certificates to fix SSL error; see http://www.chawn.com/RootCerts.htm
- powershell "md C:\temp\certs; CertUtil -generateSSTFromWU C:\temp\certs\RootStore.sst; Get-ChildItem -Path C:\\temp\certs\Rootstore.sst | Import-Certificate -CertStoreLocation Cert:\\LocalMachine\\Root\\ | out-null"
env:
- PYTHON=C:\\Python35\\python
- PYTHON=C:\\Python36\\python
- &linux_s390x_35
name: Linux | s390x | Python 3.5
- &linux_s390x_36
name: Linux | s390x | Python 3.6
language: python
python: 3.5
python: 3.6
services: docker
arch: s390x
env: PYTHON=python
allow_failures:
# must repeat the s390x job above exactly to match
- *linux_s390x_35
- *linux_s390x_36
install: $PYTHON -m pip install -r requirements-dev.txt
+7 -6
View File
@@ -1,11 +1,12 @@
This is a summary of the Python versions and platforms covered by the different CI platforms:
| | 3.5 | 3.6 | 3.7 | 3.8 |
|----------|------------------|------------------|---------------------------------------------------|------------------|
| Linux | Travis CI | CircleCI | AppVeyor / GitHub Actions | Azure Pipelines |
| macOS | Azure Pipelines | CircleCI | AppVeyor / Travis CI¹ / CircleCI / GitHub Actions | Azure Pipelines |
| Windows | TravisCI | Azure Pipelines | AppVeyor / GitHub Actions | Azure Pipelines |
| | 3.6 | 3.7 | 3.8 |
|----------|------------------------------|------------------------------------------|------------------|
| Linux | Travis CI / CircleCI | AppVeyor² / GitHub Actions | Azure Pipelines |
| macOS | CircleCI | AppVeyor² / Travis CI¹ / GitHub Actions | Azure Pipelines |
| Windows | Travis CI / Azure Pipelines | AppVeyor² / GitHub Actions | Azure Pipelines |
> ¹ Python version not really pinned, but dependent on the (default) version of image used.
> ² AppVeyor only runs the "basic" test to reduce load.
Non-x86 architectures are covered on Travis CI using Python 3.5.
Non-x86 architectures are covered on Travis CI using Python 3.6.
+76 -17
View File
@@ -22,11 +22,13 @@ What does it do?
| CPython 3.6 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅² |
| CPython 3.7 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅² |
| CPython 3.8 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅² |
| CPython 3.9³ | 🛠 | 🛠 | 🛠 | 🛠 | 🛠 | 🛠 | 🛠 | |
| PyPy 2.7 v7.3.0 | ✅ | | ✅ | ✅ | | | | |
| PyPy 3.6 v7.3.0 | ✅ | | ✅ | ✅ | | | | |
<sup>¹ Not supported on Travis</sup><br>
<sup>² Beta support until Travis CI fixes <a href="https://travis-ci.community/t/no-space-left-on-device-for-system-z/5954/11">a bug</a></sup>
<sup>² Beta support until Travis CI fixes <a href="https://travis-ci.community/t/no-space-left-on-device-for-system-z/5954/11">a bug</a></sup><br>
<sup>³ Python 3.9 is not yet ABI stable, so you shouldn't publish wheels with it yet. But if you want to check that your wheels build on Python 3.9, try our [`python3.9` branch](https://github.com/joerick/cibuildwheel/pull/382)!</sup>
- Builds manylinux, macOS and Windows wheels for CPython and PyPy using Azure Pipelines, Travis CI, AppVeyor, and CircleCI
- Bundles shared library dependencies on Linux and macOS through [auditwheel](https://github.com/pypa/auditwheel) and [delocate](https://github.com/matthew-brett/delocate)
@@ -77,7 +79,7 @@ env:
# Note: TWINE_PASSWORD is set to a PyPI API token in Travis settings
install:
- python3 -m pip install cibuildwheel==1.4.1
- python3 -m pip install cibuildwheel==1.5.3
script:
# build the wheels, put them into './wheelhouse'
@@ -92,7 +94,7 @@ after_success:
fi
```
For more information, including how to build on Appveyor, Azure, CircleCI, check out the [documentation](https://cibuildwheel.readthedocs.org) and also check out [the examples](https://github.com/joerick/cibuildwheel/tree/master/examples).
For more information, including how to build on GitHub Actions, Appveyor, Azure Pipelines, or CircleCI, check out the [documentation](https://cibuildwheel.readthedocs.org) and the [examples](https://github.com/joerick/cibuildwheel/tree/master/examples).
Options
-------
@@ -100,17 +102,20 @@ Options
| | Option | Description |
|---|--------|-------------|
| **Build selection** | [`CIBW_PLATFORM`](https://cibuildwheel.readthedocs.io/en/stable/options/#platform) | Override the auto-detected target platform |
| | [`CIBW_BUILD`](https://cibuildwheel.readthedocs.io/en/stable/options/#build-skip) [`CIBW_SKIP`](https://cibuildwheel.readthedocs.io/en/stable/options/#build-skip) | Choose the Python versions to build |
| **Build environment** | [`CIBW_ENVIRONMENT`](https://cibuildwheel.readthedocs.io/en/stable/options/#environment) | Set environment variables needed during the build |
| | [`CIBW_BUILD`](https://cibuildwheel.readthedocs.io/en/stable/options/#build-skip) <br> [`CIBW_SKIP`](https://cibuildwheel.readthedocs.io/en/stable/options/#build-skip) | Choose the Python versions to build |
| **Build customization** | [`CIBW_ENVIRONMENT`](https://cibuildwheel.readthedocs.io/en/stable/options/#environment) | Set environment variables needed during the build |
| | [`CIBW_BEFORE_ALL`](https://cibuildwheel.readthedocs.io/en/stable/options/#before-all) | Execute a shell command on the build system before any wheels are built. |
| | [`CIBW_BEFORE_BUILD`](https://cibuildwheel.readthedocs.io/en/stable/options/#before-build) | Execute a shell command preparing each wheel's build |
| | [`CIBW_REPAIR_WHEEL_COMMAND`](https://cibuildwheel.readthedocs.io/en/stable/options/#repair-wheel-command) | Execute a shell command to repair each (non-pure Python) built wheel |
| | [`CIBW_MANYLINUX_X86_64_IMAGE`](https://cibuildwheel.readthedocs.io/en/stable/options/#manylinux-image) [`CIBW_MANYLINUX_I686_IMAGE`](https://cibuildwheel.readthedocs.io/en/stable/options/#manylinux-image) [`CIBW_MANYLINUX_PYPY_X86_64_IMAGE`](https://cibuildwheel.readthedocs.io/en/stable/options/#manylinux-image) | Specify alternative manylinux docker images |
| | [`CIBW_MANYLINUX_X86_64_IMAGE`](https://cibuildwheel.readthedocs.io/en/stable/options/#manylinux-image) <br> [`CIBW_MANYLINUX_I686_IMAGE`](https://cibuildwheel.readthedocs.io/en/stable/options/#manylinux-image) <br> [`CIBW_MANYLINUX_PYPY_X86_64_IMAGE`](https://cibuildwheel.readthedocs.io/en/stable/options/#manylinux-image) <br> [`CIBW_MANYLINUX_AARCH64_IMAGE`](https://cibuildwheel.readthedocs.io/en/stable/options/#manylinux-image) <br> [`CIBW_MANYLINUX_PPC64LE_IMAGE`](https://cibuildwheel.readthedocs.io/en/stable/options/#manylinux-image) <br> [`CIBW_MANYLINUX_S390X_IMAGE`](https://cibuildwheel.readthedocs.io/en/stable/options/#manylinux-image) | Specify alternative manylinux docker images |
| | [`CIBW_DEPENDENCY_VERSIONS`](https://cibuildwheel.readthedocs.io/en/stable/options/#dependency-versions) | Specify how cibuildwheel controls the versions of the tools it uses |
| **Testing** | [`CIBW_TEST_COMMAND`](https://cibuildwheel.readthedocs.io/en/stable/options/#test-command) | Execute a shell command to test each built wheel |
| | [`CIBW_BEFORE_TEST`](https://cibuildwheel.readthedocs.io/en/stable/options/#before-test) | Execute shell command to prepare test environment |
| | [`CIBW_BEFORE_TEST`](https://cibuildwheel.readthedocs.io/en/stable/options/#before-test) | Execute a shell command before testing each wheel |
| | [`CIBW_TEST_REQUIRES`](https://cibuildwheel.readthedocs.io/en/stable/options/#test-requires) | Install Python dependencies before running the tests |
| | [`CIBW_TEST_EXTRAS`](https://cibuildwheel.readthedocs.io/en/stable/options/#test-extras) | Install your wheel for testing using extras_require |
| **Other** | [`CIBW_BUILD_VERBOSITY`](https://cibuildwheel.readthedocs.io/en/stable/options/#build-verbosity) | Increase/decrease the output of pip wheel |
Working examples
----------------
@@ -128,6 +133,10 @@ Here are some repos that use cibuildwheel.
- [Twisted](https://github.com/twisted/twisted)
- [gmic-py](https://github.com/dtschump/gmic-py)
- [creme](https://github.com/creme-ml/creme)
- [PyAV](https://github.com/PyAV-Org/PyAV)
- [aiortc](https://github.com/aiortc/aiortc)
- [aioquic](https://github.com/aiortc/aioquic)
- [pikepdf](https://github.com/pikepdf/pikepdf)
> Add your repo here! Send a PR.
@@ -143,6 +152,56 @@ This is similar to static linking, so it might have some licence implications. C
Changelog
=========
### 1.5.3
_19 July 2020_
- 🛠 Update CPython 3.8 to 3.8.3 (#405)
- 🛠 Internal refactoring of Linux build, to move control flow into Python (#386)
### 1.5.2
_8 July 2020_
- 🐛 Fix an issue on Windows where pyproject.toml would cause an error when
some requirements formats were used. (#401)
- 🛠 Update CPython 3.7 to 3.7.8 (#394)
### 1.5.1
_25 June 2020_
- 🐛 Fix "OSError: [WinError 17] The system cannot move the file to a different
disk drive" on Github Actions (#388, #389)
### 1.5.0
_24 June 2020_
- 🌟 Add [`CIBW_BEFORE_ALL`](https://cibuildwheel.readthedocs.io/en/stable/options/#before-all)
option, which lets you run a command on the build machine before any wheels
are built. This is especially useful when building on Linux, to `make`
something external to Python, or to `yum install` a dependency. (#342)
- ✨ Added support for projects using pyproject.toml instead of setup.py
(#360, #358)
- ✨ Added workaround to allow Python 3.5 on Windows to pull dependencies from
pyproject.toml. (#358)
- 📚 Improved Github Actions examples and docs (#354, #362)
- 🐛 Ensure pip wheel uses the specified package, and doesn't build a wheel
from PyPI (#369)
- 🛠 Internal changes: using pathlib.Path, precommit hooks, testing
improvements.
### 1.4.2
_25 May 2020_
- 🛠 Dependency updates, including CPython 3.8.3 & manylinux images.
- 🛠 Lots of internal updates - type annotations and checking using mypy, and
a new integration testing system.
- ⚠️ Removed support for *running* cibuildwheel using Python 3.5. cibuildwheel
will continue to build Python 3.5 wheels until EOL.
### 1.4.1
_4 May 2020_
@@ -155,7 +214,7 @@ _4 May 2020_
_2 May 2020_
- Deterministic builds. cibuildwheel now locks the versions of the tools it
- 🌟 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
@@ -185,7 +244,7 @@ _2 May 2020_
_12 March 2020_
- Add support for building on Github Actions! Check out the
- 🌟 Add support for building on Github Actions! Check out the
[docs](https://cibuildwheel.readthedocs.io/en/stable/setup/#github-actions)
for information on how to set it up. (#194)
- ✨ Add the `CIBW_BEFORE_TEST` option, which lets you run a command to
@@ -195,9 +254,9 @@ _12 March 2020_
_8 March 2020_
- Add support for building PyPy wheels, across Manylinux, macOS, and
- 🌟 Add support for building PyPy wheels, across Manylinux, macOS, and
Windows. (#185)
- Added the ability to build ARM64 (aarch64), ppc64le, and s390x wheels,
- 🌟 Added the ability to build ARM64 (aarch64), ppc64le, and s390x wheels,
using manylinux2014 and Travis CI. (#273)
- ✨ You can now build macOS wheels on Appveyor. (#230)
- 🛠 Changed default macOS minimum target to 10.9, from 10.6. This allows the
@@ -218,7 +277,7 @@ _8 March 2020_
_7 December 2019_
- Add support for building manylinux2014 wheels. To use, set
- 🌟 Add support for building manylinux2014 wheels. To use, set
`CIBW_MANYLINUX_X86_64_IMAGE` and CIBW_MANYLINUX_I686_IMAGE to
`manylinux2014`.
- ✨ Add support for [Linux on Appveyor](https://www.appveyor.com/blog/2018/03/06/appveyor-for-linux/) (#204, #207)
@@ -231,8 +290,8 @@ _7 December 2019_
_10 November 2019_
- Add support for building Python 3.8 wheels! (#180)
- Add support for building manylinux2010 wheels. cibuildwheel will now
- 🌟 Add support for building Python 3.8 wheels! (#180)
- 🌟 Add support for building manylinux2010 wheels. cibuildwheel will now
build using the manylinux2010 images by default. If your project is still
manylinux1 compatible, you should get both manylinux1 and manylinux2010
wheels - you can upload both to PyPI. If you always require manylinux1 wheels, you can
@@ -268,7 +327,7 @@ _28 May 2019_
_26 May 2019_
- Add support for building on Azure pipelines! This lets you build all
- 🌟 Add support for building on Azure pipelines! This lets you build all
Linux, Mac and Windows wheels on one service, so it promises to be the
easiest to set up! Check out the quickstart in the docs, or
[cibuildwheel-azure-example](https://github.com/joerick/cibuildwheel-azure-example)
@@ -298,8 +357,8 @@ _3 February 2019_
_23 September 2018_
- Add `CIBW_BUILD` option, for specifying which specific builds to perform (#101)
- Add support for building Mac and Linux on CircleCI (#91, #97)
- 🌟 Add `CIBW_BUILD` option, for specifying which specific builds to perform (#101)
- 🌟 Add support for building Mac and Linux on CircleCI (#91, #97)
- 🛠 Improved support for building universal wheels (#95)
- 🛠 Ensure log output is unbuffered and therefore in the correct order (#92)
- 🛠 Improved error reporting for errors that occur inside a package's setup.py (#88)
+5
View File
@@ -10,6 +10,11 @@ environment:
- APPVEYOR_BUILD_WORKER_IMAGE: macos-mojave
APPVEYOR_JOB_NAME: "python37-x64-macos-mojave"
# while pypy libffi dependency is failing the build, allow failures on macos
matrix:
allow_failures:
- APPVEYOR_BUILD_WORKER_IMAGE: macos-mojave
stack: python 3.7
build: off
+4 -15
View File
@@ -6,17 +6,7 @@ jobs:
inputs:
versionSpec: '3.8'
- bash: |
python -m pip install -r requirements-dev.txt
python ./bin/run_tests.py
- job: macos_35
pool: {vmImage: 'macOS-10.15'}
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '3.5'
- bash: |
python -m pip install -r requirements-dev.txt
python -m pip install -r requirements-dev.txt pytest-azurepipelines
python ./bin/run_tests.py
- job: macos_38
@@ -26,7 +16,7 @@ jobs:
inputs:
versionSpec: '3.8'
- bash: |
python -m pip install -r requirements-dev.txt
python -m pip install -r requirements-dev.txt pytest-azurepipelines
python ./bin/run_tests.py
- job: windows_36
@@ -38,7 +28,7 @@ jobs:
- script: choco install vcpython27 -f -y
displayName: Install Visual C++ for Python 2.7
- bash: |
python -m pip install -r requirements-dev.txt
python -m pip install -r requirements-dev.txt pytest-azurepipelines
python ./bin/run_tests.py
- job: windows_38
@@ -50,6 +40,5 @@ jobs:
- script: choco install vcpython27 -f -y
displayName: Install Visual C++ for Python 2.7
- bash: |
python -m pip install -r requirements-dev.txt
python -m pip install -r requirements-dev.txt pytest-azurepipelines
python ./bin/run_tests.py
+1 -4
View File
@@ -1,6 +1,3 @@
#!/bin/bash
cd "$(dirname "$0")"
cd ..
CIBW_PLATFORM=linux ./bin/run_test.py $1
CIBW_PLATFORM=linux pytest "$@"
-27
View File
@@ -1,27 +0,0 @@
#!/usr/bin/env python3
import argparse
import os
import subprocess
import sys
def single_run(test_project):
# run the test
subprocess.check_call(
[sys.executable, '-m', 'pytest', '-vv', os.path.join(test_project, 'cibuildwheel_test.py')],
)
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument("test_project_dir")
args = parser.parse_args()
project_path = os.path.abspath(args.test_project_dir)
if not os.path.exists(project_path):
print('No test project not found.', file=sys.stderr)
exit(2)
single_run(project_path)
+8 -18
View File
@@ -3,28 +3,18 @@
import os
import subprocess
import sys
from glob import glob
from pathlib import Path
if __name__ == '__main__':
# move cwd to the project root
os.chdir(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
os.chdir(Path(__file__).resolve().parents[1])
# run the unit tests
subprocess.check_call([sys.executable, '-m', 'pytest', 'unit_test'])
unit_test_args = [sys.executable, '-m', 'pytest', 'unit_test']
# run the docker unit tests only on Linux
if sys.platform.startswith('linux'):
unit_test_args += ['--run-docker']
subprocess.check_call(unit_test_args)
# run the integration tests
test_projects = sorted(glob('test/??_*'))
if len(test_projects) == 0:
print('No test projects found. Aborting.', file=sys.stderr)
exit(2)
print('Testing projects:', test_projects)
run_test_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'run_test.py')
for project_path in test_projects:
subprocess.check_call([sys.executable, run_test_path, project_path])
print('%d projects built successfully.' % len(test_projects))
subprocess.check_call([sys.executable, '-m', 'pytest', '-x', '--durations', '0', 'test'])
+1 -1
View File
@@ -1 +1 @@
__version__ = '1.4.1'
__version__ = '1.5.3'
+67 -62
View File
@@ -4,6 +4,9 @@ import sys
import textwrap
import traceback
from configparser import ConfigParser
from pathlib import Path
from typing import Any, Dict, List, Optional, overload
import cibuildwheel
import cibuildwheel.linux
@@ -14,14 +17,19 @@ from cibuildwheel.environment import (
parse_environment,
)
from cibuildwheel.util import (
BuildOptions,
BuildSelector,
DependencyConstraints,
Unbuffered,
BuildOptions
resources_dir,
)
def get_option_from_environment(option_name, platform=None, default=None):
@overload
def get_option_from_environment(option_name: str, platform: Optional[str], default: str) -> str: ... # noqa: E704
@overload
def get_option_from_environment(option_name: str, platform: Optional[str] = None, default: None = None) -> Optional[str]: ... # noqa: E704 E302
def get_option_from_environment(option_name: str, platform: Optional[str] = None, default: Optional[str] = None) -> Optional[str]: # noqa: E302
'''
Returns an option from the environment, optionally scoped by the platform.
@@ -32,20 +40,20 @@ def get_option_from_environment(option_name, platform=None, default=None):
CIBW_COLOR.
'''
if platform:
option = os.environ.get('%s_%s' % (option_name, platform.upper()))
option = os.environ.get(f'{option_name}_{platform.upper()}')
if option is not None:
return option
return os.environ.get(option_name, default)
def strtobool(val):
def strtobool(val: str) -> bool:
if val.lower() in ('y', 'yes', 't', 'true', 'on', '1'):
return True
return False
def main():
def main() -> None:
parser = argparse.ArgumentParser(
description='Build wheels for all the platforms.',
epilog='''
@@ -108,65 +116,69 @@ def main():
file=sys.stderr)
exit(2)
output_dir = args.output_dir
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='')
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', '')
package_dir = Path(args.package_dir)
output_dir = Path(args.output_dir)
if platform == 'linux':
repair_command_default = 'auditwheel repair -w {dest_dir} {wheel}'
elif platform == 'macos':
repair_command_default = 'delocate-listdeps {wheel} && delocate-wheel --require-archs x86_64 -w {dest_dir} {wheel}'
else:
repair_command_default = ''
repair_command = get_option_from_environment('CIBW_REPAIR_WHEEL_COMMAND', platform=platform, default=repair_command_default)
build_config, skip_config = os.environ.get('CIBW_BUILD', '*'), os.environ.get('CIBW_SKIP', '')
environment_config = get_option_from_environment('CIBW_ENVIRONMENT', platform=platform, default='')
before_test = get_option_from_environment('CIBW_BEFORE_TEST', platform=platform, default='')
before_all = get_option_from_environment('CIBW_BEFORE_ALL', platform=platform, default='')
before_build = get_option_from_environment('CIBW_BEFORE_BUILD', platform=platform)
repair_command = get_option_from_environment('CIBW_REPAIR_WHEEL_COMMAND', platform=platform, default=repair_command_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)
test_command = get_option_from_environment('CIBW_TEST_COMMAND', platform=platform)
before_test = get_option_from_environment('CIBW_BEFORE_TEST', 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='')
build_verbosity_str = get_option_from_environment('CIBW_BUILD_VERBOSITY', platform=platform, default='')
if test_extras:
test_extras = '[{0}]'.format(test_extras)
try:
build_verbosity = min(3, max(-3, int(build_verbosity)))
except ValueError:
build_verbosity = 0
build_selector = BuildSelector(build_config, skip_config)
try:
environment = parse_environment(environment_config)
except (EnvironmentParseError, ValueError):
print('cibuildwheel: Malformed environment option "%s"' % environment_config, file=sys.stderr)
print(f'cibuildwheel: Malformed environment option "{environment_config}"', file=sys.stderr)
traceback.print_exc(None, sys.stderr)
exit(2)
build_selector = BuildSelector(build_config, skip_config)
if dependency_versions == 'pinned':
dependency_constraints: Optional[DependencyConstraints] = DependencyConstraints.with_defaults()
elif dependency_versions == 'latest':
dependency_constraints = None
else:
dependency_versions_path = Path(dependency_versions)
dependency_constraints = DependencyConstraints(dependency_versions_path)
if test_extras:
test_extras = f'[{test_extras}]'
try:
build_verbosity = min(3, max(-3, int(build_verbosity_str)))
except ValueError:
build_verbosity = 0
# Add CIBUILDWHEEL environment variable
# 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(package_dir, 'setup.py')):
print('cibuildwheel: Could not find setup.py at root of package', file=sys.stderr)
if not any((package_dir / name).exists()
for name in ["setup.py", "setup.cfg", "pyproject.toml"]):
print('cibuildwheel: Could not find setup.py, setup.cfg or pyproject.toml at root of package', file=sys.stderr)
exit(2)
if args.print_build_identifiers:
print_build_identifiers(platform, build_selector)
exit(0)
manylinux_images: Optional[Dict[str, str]] = None
if platform == 'linux':
pinned_docker_images_file = os.path.join(
os.path.dirname(__file__), 'resources', 'pinned_docker_images.cfg'
)
pinned_docker_images_file = resources_dir / '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.
@@ -180,7 +192,7 @@ def main():
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_name = f'CIBW_MANYLINUX_{build_platform.upper()}_IMAGE'
config_value = os.environ.get(config_name)
if config_value is None:
@@ -193,32 +205,30 @@ def main():
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,
test_extras=test_extras,
before_test=before_test,
before_build=before_build,
before_all=before_all,
build_verbosity=build_verbosity,
build_selector=build_selector,
repair_command=repair_command,
environment=environment,
before_test=before_test,
dependency_constraints=dependency_constraints,
manylinux_images=manylinux_images,
)
# Python is buffering by default when running on the CI platforms, giving problems interleaving subprocess call output with unflushed calls to 'print'
sys.stdout = Unbuffered(sys.stdout)
sys.stdout = Unbuffered(sys.stdout) # type: ignore
print_preamble(platform, build_options)
if not os.path.exists(output_dir):
os.makedirs(output_dir)
if not output_dir.exists():
output_dir.mkdir(parents=True)
if platform == 'linux':
cibuildwheel.linux.build(build_options)
@@ -227,21 +237,21 @@ def main():
elif platform == 'macos':
cibuildwheel.macos.build(build_options)
else:
print('cibuildwheel: Unsupported platform: {}'.format(platform), file=sys.stderr)
print(f'cibuildwheel: Unsupported platform: {platform}', file=sys.stderr)
exit(2)
def detect_obsolete_options():
def detect_obsolete_options() -> None:
# Check the old 'MANYLINUX1_*_IMAGE' options
for (deprecated, alternative) in [('CIBW_MANYLINUX1_X86_64_IMAGE', 'CIBW_MANYLINUX_X86_64_IMAGE'),
('CIBW_MANYLINUX1_I686_IMAGE', 'CIBW_MANYLINUX_I686_IMAGE')]:
if deprecated in os.environ:
print("'{}' has been deprecated, and will be removed in a future release. Use the option '{}' instead.".format(deprecated, alternative))
print(f"'{deprecated}' has been deprecated, and will be removed in a future release. Use the option '{alternative}' instead.")
if alternative not in os.environ:
print("Using value of option '{}' as replacement for '{}'".format(deprecated, alternative))
print(f"Using value of option '{deprecated}' as replacement for '{alternative}'")
os.environ[alternative] = os.environ[deprecated]
else:
print("Option '{}' is not empty. Please unset '{}'".format(alternative, deprecated))
print(f"Option '{alternative}' is not empty. Please unset '{deprecated}'")
exit(2)
# Check for deprecated identifiers in 'CIBW_BUILD' and 'CIBW_SKIP' options
@@ -250,15 +260,11 @@ def detect_obsolete_options():
('macosx_10_6_intel', 'macosx_x86_64'),
('macosx_10_9_x86_64', 'macosx_x86_64')]:
if option in os.environ and deprecated in os.environ[option]:
print("Build identifiers with '{deprecated}' have been deprecated. Replacing all occurences of '{deprecated}' with '{alternative}' in the option '{option}'".format(
deprecated=deprecated,
alternative=alternative,
option=option,
))
print(f"Build identifiers with '{deprecated}' have been deprecated. Replacing all occurences of '{deprecated}' with '{alternative}' in the option '{option}'")
os.environ[option] = os.environ[option].replace(deprecated, alternative)
def print_preamble(platform, build_options):
def print_preamble(platform: str, build_options: BuildOptions) -> None:
print(textwrap.dedent('''
_ _ _ _ _ _ _
___|_| |_ _ _|_| |_| |_ _ _| |_ ___ ___| |
@@ -266,12 +272,12 @@ def print_preamble(platform, build_options):
|___|_|___|___|_|_|___|_____|_|_|___|___|_|
'''))
print('cibuildwheel version %s\n' % cibuildwheel.__version__)
print(f'cibuildwheel version {cibuildwheel.__version__}\n')
print('Build options:')
print(' platform: %r' % platform)
print(f' platform: {platform!r}')
for option, value in sorted(build_options._asdict().items()):
print(' %s: %r' % (option, value))
print(f' {option}: {value!r}')
warnings = detect_warnings(platform, build_options)
if warnings:
@@ -282,21 +288,20 @@ def print_preamble(platform, build_options):
print('\nHere we go!\n')
def print_build_identifiers(platform, build_selector):
def print_build_identifiers(platform: str, build_selector: BuildSelector) -> None:
python_configurations: List[Any] = []
if platform == 'linux':
python_configurations = cibuildwheel.linux.get_python_configurations(build_selector)
elif platform == 'windows':
python_configurations = cibuildwheel.windows.get_python_configurations(build_selector)
elif platform == 'macos':
python_configurations = cibuildwheel.macos.get_python_configurations(build_selector)
else:
python_configurations = []
for config in python_configurations:
print(config.identifier)
def detect_warnings(platform, build_options):
def detect_warnings(platform: str, build_options: BuildOptions) -> List[str]:
warnings = []
# warn about deprecated {python} and {pip}
+65 -29
View File
@@ -1,13 +1,24 @@
import shlex
import subprocess
from collections import namedtuple
from typing import Callable, Dict, List, NamedTuple, Optional, Sequence
import bashlex
import bashlex # type: ignore
NodeExecutionContext = namedtuple('NodeExecutionContext', ['environment', 'input'])
# a function that takes a shell command and the environment, and returns the result
EnvironmentExecutor = Callable[[str, Dict[str, str]], str]
def evaluate(value, environment):
def local_environment_executor(command: str, env: Dict[str, str]) -> str:
return subprocess.check_output(shlex.split(command), env=env, universal_newlines=True)
class NodeExecutionContext(NamedTuple):
environment: Dict[str, str]
input: str
executor: EnvironmentExecutor
def evaluate(value: str, environment: Dict[str, str], executor: Optional[EnvironmentExecutor] = None) -> str:
if not value:
# empty string evaluates to empty string
# (but trips up bashlex)
@@ -16,55 +27,80 @@ def evaluate(value, environment):
command_node = bashlex.parsesingle(value)
if len(command_node.parts) != 1:
raise ValueError('"%s" has too many parts' % value)
raise ValueError(f'"{value}" has too many parts')
value_word_node = command_node.parts[0]
return evaluate_node(
value_word_node,
context=NodeExecutionContext(environment=environment, input=value)
context=NodeExecutionContext(environment=environment, input=value, executor=executor or local_environment_executor)
)
def evaluate_node(node, context):
def evaluate_node(node: bashlex.ast.node, context: NodeExecutionContext) -> str:
if node.kind == 'word':
return evaluate_word_node(node, context=context)
elif node.kind == 'commandsubstitution':
return evaluate_command_node(node.command, context=context)
node_result = evaluate_command_node(node.command, context=context)
# bash removes training newlines in command substitution
return node_result.rstrip()
elif node.kind == 'parameter':
return evaluate_parameter_node(node, context=context)
else:
raise ValueError('Unsupported bash construct: "%s"' % node.word)
raise ValueError(f'Unsupported bash construct: "{node.kind}"')
def evaluate_word_node(node, context):
word_start = node.pos[0]
word_end = node.pos[1]
word_string = context.input[word_start:word_end]
letters = list(word_string)
def evaluate_word_node(node: bashlex.ast.node, context: NodeExecutionContext) -> str:
value = node.word
for part in node.parts:
part_start = part.pos[0] - word_start
part_end = part.pos[1] - word_start
part_string = context.input[part.pos[0]:part.pos[1]]
part_value = evaluate_node(part, context=context)
# Set all the characters in the part to None
for i in range(part_start, part_end):
letters[i] = None
if part_string not in value:
raise RuntimeError(
'bash parse failed. part "{}" not found in "{}". Word was "{}". Full input was "{}"'.format(
part_string, value, node.word, context.input,
)
)
letters[part_start] = evaluate_node(part, context=context)
value = value.replace(part_string, part_value, 1)
# remove the None letters and concat
value = ''.join(l for l in letters if l is not None)
# apply bash-like quotes/whitespace treatment
return ' '.join(word.strip() for word in shlex.split(value))
return value
def evaluate_command_node(node, context):
words = [evaluate_node(part, context=context) for part in node.parts]
def evaluate_command_node(node: bashlex.ast.node, context: NodeExecutionContext) -> str:
if any(n.kind == 'operator' for n in node.parts):
return evaluate_nodes_as_compound_command(node.parts, context=context)
else:
return evaluate_nodes_as_simple_command(node.parts, context=context)
def evaluate_nodes_as_compound_command(nodes: Sequence[bashlex.ast.node], context: NodeExecutionContext) -> str:
# bashlex doesn't support any operators besides ';' inside command
# substitutions, so we only need to handle that case. We do so assuming
# that `set -o errexit` is on, because it's easier to code!
result = ''
for node in nodes:
if node.kind == 'command':
result += evaluate_command_node(node, context=context)
elif node.kind == 'operator':
if node.op == ';':
pass
else:
raise ValueError(f'Unsupported bash operator: "{node.op}"')
else:
raise ValueError(f'Unsupported bash node in compound command: "{node.kind}"')
return result
def evaluate_nodes_as_simple_command(nodes: List[bashlex.ast.node], context: NodeExecutionContext):
words = [evaluate_node(part, context=context) for part in nodes]
command = ' '.join(words)
return subprocess.check_output(shlex.split(command), env=context.environment, universal_newlines=True)
return context.executor(command, context.environment)
def evaluate_parameter_node(node, context):
def evaluate_parameter_node(node: bashlex.ast.node, context: NodeExecutionContext) -> str:
return context.environment.get(node.value, '')
+189
View File
@@ -0,0 +1,189 @@
import io
import json
import os
import shlex
import subprocess
import sys
import uuid
from os import PathLike
from pathlib import Path, PurePath
from typing import IO, Dict, List, Optional, Sequence, Union
class DockerContainer:
'''
An object that represents a running Docker container.
Intended for use as a context manager e.g.
`with DockerContainer('ubuntu') as docker:`
A bash shell is running in the remote container. When `call()` is invoked,
the command is relayed to the remote shell, and the results are streamed
back to cibuildwheel.
'''
UTILITY_PYTHON = '/opt/python/cp38-cp38/bin/python'
process: subprocess.Popen
bash_stdin: IO[bytes]
bash_stdout: IO[bytes]
def __init__(self, docker_image: str, simulate_32_bit=False):
self.docker_image = docker_image
self.simulate_32_bit = simulate_32_bit
def __enter__(self) -> 'DockerContainer':
self.name = f'cibuildwheel-{uuid.uuid4()}'
shell_args = ['linux32', '/bin/bash'] if self.simulate_32_bit else ['/bin/bash']
subprocess.run(
[
'docker', 'create',
'--env', 'CIBUILDWHEEL',
'--name', self.name,
'-i',
'-v', '/:/host', # ignored on CircleCI
self.docker_image,
*shell_args
],
check=True,
)
self.process = subprocess.Popen(
[
'docker', 'start',
'--attach', '--interactive',
self.name,
],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
)
assert self.process.stdin and self.process.stdout
self.bash_stdin = self.process.stdin
self.bash_stdout = self.process.stdout
# run a noop command to block until the container is responding
self.call(['/bin/true'])
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.bash_stdin.close()
self.process.terminate()
self.process.wait()
subprocess.run(['docker', 'rm', '--force', '-v', self.name])
self.name = None
def copy_into(self, from_path: Path, to_path: PurePath) -> None:
# `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.
if from_path.is_dir():
self.call(['mkdir', '-p', to_path])
subprocess.run(
f'tar cf - . | docker exec -i {self.name} tar -xC {shell_quote(to_path)} -f -',
shell=True,
check=True,
cwd=from_path)
else:
subprocess.run(
f'cat {shell_quote(from_path)} | docker exec -i {self.name} sh -c "cat > {shell_quote(to_path)}"',
shell=True,
check=True)
def copy_out(self, from_path: PurePath, to_path: Path) -> None:
# note: we assume from_path is a dir
to_path.mkdir(parents=True, exist_ok=True)
subprocess.run(
f'docker exec -i {self.name} tar -cC {shell_quote(from_path)} -f - . | tar -xf -',
shell=True,
check=True,
cwd=to_path
)
def glob(self, path: PurePath, pattern: str) -> List[PurePath]:
glob_pattern = os.path.join(str(path), pattern)
path_strs = json.loads(self.call([
self.UTILITY_PYTHON,
'-c',
f'import sys, json, glob; json.dump(glob.glob({glob_pattern!r}), sys.stdout)'
], capture_output=True))
return [PurePath(p) for p in path_strs]
def call(self, args: Sequence[Union[str, PathLike]], env: Dict[str, str] = {},
capture_output=False, cwd: Optional[Union[str, PathLike]] = None) -> str:
chdir = f'cd {cwd}' if cwd else ''
env_assignments = ' '.join(f'{shlex.quote(k)}={shlex.quote(v)}'
for k, v in env.items())
command = ' '.join(shlex.quote(str(a)) for a in args)
end_of_message = str(uuid.uuid4())
# log the command we're executing
print(f' + {command}')
# Write a command to the remote shell. First we change the
# cwd, if that's required. Then, we use the `env` utility to run
# `command` inside the specified environment. We use `env` because it
# can cope with spaces and strange characters in the name or value.
# Finally, the remote shell is told to write a footer - this will show
# up in the output so we know when to stop reading, and will include
# the returncode of `command`.
self.bash_stdin.write(bytes(f'''(
{chdir}
env {env_assignments} {command}
printf "%04d%s\n" $? {end_of_message}
)
''', encoding='utf8', errors='surrogateescape'))
self.bash_stdin.flush()
if capture_output:
output_io: IO[bytes] = io.BytesIO()
else:
output_io = sys.stdout.buffer
while True:
line = self.bash_stdout.readline()
if line.endswith(b'%s\n' % (bytes(end_of_message, encoding='utf8'))):
footer_offset = (
len(line)
- 1 # newline character
- len(end_of_message) # delimiter
- 4 # 4 returncode decimals
)
returncode_str = line[footer_offset:footer_offset+4]
returncode = int(returncode_str)
# add the last line to output, without the footer
output_io.write(line[0:footer_offset])
break
else:
output_io.write(line)
if isinstance(output_io, io.BytesIO):
output = str(output_io.getvalue(), encoding='utf8', errors='surrogateescape')
else:
output = ''
if returncode != 0:
raise subprocess.CalledProcessError(returncode, args, output)
return output
def get_environment(self) -> Dict[str, str]:
return json.loads(self.call([
self.UTILITY_PYTHON,
'-c',
'import sys, json, os; json.dump(os.environ.copy(), sys.stdout)'
], capture_output=True))
def environment_executor(self, command: str, environment: Dict[str, str]) -> str:
# used as an EnvironmentExecutor to evaluate commands and capture output
return self.call(shlex.split(command), env=environment)
def shell_quote(path: PurePath) -> str:
return shlex.quote(str(path))
+26 -22
View File
@@ -1,4 +1,6 @@
import bashlex
import bashlex # type: ignore
from typing import Dict, List, Mapping, Optional
from . import bashlex_eval
@@ -7,13 +9,7 @@ class EnvironmentParseError(Exception):
pass
def parse_environment(env_string):
env_items = split_env_items(env_string)
assignments = [EnvironmentAssignment(item) for item in env_items]
return ParsedEnvironment(assignments=assignments)
def split_env_items(env_string):
def split_env_items(env_string: str) -> List[str]:
'''Splits space-separated variable assignments into a list of individual assignments.
>>> split_env_items('VAR=abc')
@@ -43,39 +39,47 @@ def split_env_items(env_string):
class EnvironmentAssignment:
def __init__(self, assignment):
def __init__(self, assignment: str):
name, equals, value = assignment.partition('=')
if not equals:
raise EnvironmentParseError(assignment)
self.name = name
self.value = value
def evaluated_value(self, environment):
def evaluated_value(self, environment: Dict[str, str], executor: Optional[bashlex_eval.EnvironmentExecutor] = None) -> str:
'''Returns the value of this assignment, as evaluated in the environment'''
return bashlex_eval.evaluate(self.value, environment=environment)
return bashlex_eval.evaluate(self.value, environment=environment, executor=executor)
def as_shell_assignment(self):
return 'export %s=%s' % (self.name, self.value)
def as_shell_assignment(self) -> str:
return f'export {self.name}={self.value}'
def __repr__(self):
return '%s=%s' % (self.name, self.value)
def __repr__(self) -> str:
return f'{self.name}={self.value}'
class ParsedEnvironment:
def __init__(self, assignments):
def __init__(self, assignments: List[EnvironmentAssignment]):
self.assignments = assignments
def as_dictionary(self, prev_environment):
environment = prev_environment.copy()
def as_dictionary(self,
prev_environment: Mapping[str, str],
executor: Optional[bashlex_eval.EnvironmentExecutor] = None) -> Dict[str, str]:
environment = dict(**prev_environment)
for assignment in self.assignments:
value = assignment.evaluated_value(environment=environment)
value = assignment.evaluated_value(environment=environment, executor=executor)
environment[assignment.name] = value
return environment
def as_shell_commands(self):
def as_shell_commands(self) -> List[str]:
return [a.as_shell_assignment() for a in self.assignments]
def __repr__(self):
return 'ParsedEnvironment(%r)' % [repr(a) for a in self.assignments]
def __repr__(self) -> str:
return f'ParsedEnvironment({[repr(a) for a in self.assignments]!r})'
def parse_environment(env_string: str) -> ParsedEnvironment:
env_items = split_env_items(env_string)
assignments = [EnvironmentAssignment(item) for item in env_items]
return ParsedEnvironment(assignments=assignments)
+145 -196
View File
@@ -1,27 +1,17 @@
import os
import platform
import shlex
import subprocess
import sys
import textwrap
import uuid
from collections import namedtuple
from os import PathLike
from pathlib import Path, PurePath
from typing import List, NamedTuple, Union
from .util import (
get_build_verbosity_extra_flags,
prepare_command,
BuildOptions
)
from .docker_container import DockerContainer
from .util import (BuildOptions, BuildSelector,
get_build_verbosity_extra_flags, prepare_command)
def call(args, input=None, universal_newlines=False):
print('+ ' + ' '.join(shlex.quote(a) for a in args))
subprocess.run(
args, input=input, universal_newlines=universal_newlines, check=True
)
def matches_platform(identifier):
def matches_platform(identifier: str) -> bool:
pm = platform.machine()
if pm == "x86_64":
# x86_64 machines can run i686 docker containers
@@ -42,41 +32,50 @@ def matches_platform(identifier):
return False
def get_python_configurations(build_selector):
PythonConfiguration = namedtuple('PythonConfiguration', ['version', 'identifier', 'path'])
class PythonConfiguration(NamedTuple):
version: str
identifier: str
path_str: str
@property
def path(self):
return PurePath(self.path_str)
def get_python_configurations(build_selector: BuildSelector) -> List[PythonConfiguration]:
python_configurations = [
PythonConfiguration(version='2.7', identifier='cp27-manylinux_x86_64', path='/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'),
PythonConfiguration(version='2.7', identifier='cp27-manylinux_x86_64', path_str='/opt/python/cp27-cp27m'),
PythonConfiguration(version='2.7', identifier='cp27-manylinux_x86_64', path_str='/opt/python/cp27-cp27mu'),
PythonConfiguration(version='3.5', identifier='cp35-manylinux_x86_64', path_str='/opt/python/cp35-cp35m'),
PythonConfiguration(version='3.6', identifier='cp36-manylinux_x86_64', path_str='/opt/python/cp36-cp36m'),
PythonConfiguration(version='3.7', identifier='cp37-manylinux_x86_64', path_str='/opt/python/cp37-cp37m'),
PythonConfiguration(version='3.8', identifier='cp38-manylinux_x86_64', path_str='/opt/python/cp38-cp38'),
PythonConfiguration(version='2.7', identifier='cp27-manylinux_i686', path_str='/opt/python/cp27-cp27m'),
PythonConfiguration(version='2.7', identifier='cp27-manylinux_i686', path_str='/opt/python/cp27-cp27mu'),
PythonConfiguration(version='3.5', identifier='cp35-manylinux_i686', path_str='/opt/python/cp35-cp35m'),
PythonConfiguration(version='3.6', identifier='cp36-manylinux_i686', path_str='/opt/python/cp36-cp36m'),
PythonConfiguration(version='3.7', identifier='cp37-manylinux_i686', path_str='/opt/python/cp37-cp37m'),
PythonConfiguration(version='3.8', identifier='cp38-manylinux_i686', path_str='/opt/python/cp38-cp38'),
PythonConfiguration(version='2.7', identifier='pp27-manylinux_x86_64', path_str='/opt/python/pp27-pypy_73'),
PythonConfiguration(version='3.6', identifier='pp36-manylinux_x86_64', path_str='/opt/python/pp36-pypy36_pp73'),
PythonConfiguration(version='3.5', identifier='cp35-manylinux_aarch64', path_str='/opt/python/cp35-cp35m'),
PythonConfiguration(version='3.6', identifier='cp36-manylinux_aarch64', path_str='/opt/python/cp36-cp36m'),
PythonConfiguration(version='3.7', identifier='cp37-manylinux_aarch64', path_str='/opt/python/cp37-cp37m'),
PythonConfiguration(version='3.8', identifier='cp38-manylinux_aarch64', path_str='/opt/python/cp38-cp38'),
PythonConfiguration(version='3.5', identifier='cp35-manylinux_ppc64le', path_str='/opt/python/cp35-cp35m'),
PythonConfiguration(version='3.6', identifier='cp36-manylinux_ppc64le', path_str='/opt/python/cp36-cp36m'),
PythonConfiguration(version='3.7', identifier='cp37-manylinux_ppc64le', path_str='/opt/python/cp37-cp37m'),
PythonConfiguration(version='3.8', identifier='cp38-manylinux_ppc64le', path_str='/opt/python/cp38-cp38'),
PythonConfiguration(version='3.5', identifier='cp35-manylinux_s390x', path_str='/opt/python/cp35-cp35m'),
PythonConfiguration(version='3.6', identifier='cp36-manylinux_s390x', path_str='/opt/python/cp36-cp36m'),
PythonConfiguration(version='3.7', identifier='cp37-manylinux_s390x', path_str='/opt/python/cp37-cp37m'),
PythonConfiguration(version='3.8', identifier='cp38-manylinux_s390x', path_str='/opt/python/cp38-cp38'),
]
# skip builds as required
return [c for c in python_configurations if matches_platform(c.identifier) and build_selector(c.identifier)]
def build(options: BuildOptions):
def build(options: BuildOptions) -> None:
try:
subprocess.check_call(['docker', '--version'])
except Exception:
@@ -86,6 +85,7 @@ def build(options: BuildOptions):
file=sys.stderr)
exit(2)
assert options.manylinux_images is not None
python_configurations = get_python_configurations(options.build_selector)
platforms = [
('cp', 'manylinux_x86_64', options.manylinux_images['x86_64']),
@@ -96,194 +96,143 @@ def build(options: BuildOptions):
('pp', 'manylinux_x86_64', options.manylinux_images['pypy_x86_64']),
]
if not os.path.realpath(options.package_dir).startswith(os.path.realpath('.')):
cwd = Path.cwd()
abs_package_dir = options.package_dir.resolve()
if cwd != abs_package_dir and cwd not in abs_package_dir.parents:
raise Exception('package_dir must be inside the working directory')
container_package_dir = os.path.join('/project', os.path.relpath(options.package_dir, '.'))
container_package_dir = PurePath('/project') / abs_package_dir.relative_to(cwd)
container_output_dir = PurePath('/output')
for implementation, platform_tag, docker_image in platforms:
platform_configs = [c for c in python_configurations if c.identifier.startswith(implementation) and c.identifier.endswith(platform_tag)]
if not platform_configs:
continue
container_name = 'cibuildwheel-{}'.format(uuid.uuid4())
try:
shell_cmd = ['linux32', '/bin/bash'] if platform_tag.endswith("i686") else ['/bin/bash']
call(['docker', 'create',
'--env', 'CIBUILDWHEEL',
'--name', container_name,
'-i',
'-v', '/:/host', # ignored on CircleCI
docker_image,
'/bin/bash'])
with DockerContainer(docker_image, simulate_32_bit=platform_tag.endswith('i686')) as docker:
docker.copy_into(Path.cwd(), Path('/project'))
call(['docker', 'cp', '.', container_name + ':/project'])
if options.before_all:
env = docker.get_environment()
env['PATH'] = f'/opt/python/cp38-cp38:{env["PATH"]}'
env = options.environment.as_dictionary(env, executor=docker.environment_executor)
call(['docker', 'start', container_name])
before_all_prepared = prepare_command(options.before_all, project='/project', package=container_package_dir)
docker.call(['sh', '-c', before_all_prepared], env=env)
for config in platform_configs:
if options.dependency_constraints:
constraints_file = options.dependency_constraints.get_for_python_version(config.version)
for config in platform_configs:
dependency_constraint_flags: List[Union[str, PathLike]] = []
# `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(),
)
if options.dependency_constraints:
constraints_file = options.dependency_constraints.get_for_python_version(config.version)
container_constraints_file = PurePath('/constraints.txt')
call(
['docker', 'exec', '-i', container_name] + shell_cmd,
universal_newlines=True,
input='''
# give xtrace output an extra level of indent inside docker
PS4=' + '
docker.copy_into(constraints_file, container_constraints_file)
dependency_constraint_flags = ['-c', container_constraints_file]
set -o errexit
set -o xtrace
mkdir -p /output
cd /project
env = docker.get_environment()
PYBIN="{config_python_bin}"
# put this config's python top of the list
python_bin = config.path / 'bin'
env['PATH'] = f'{python_bin}:{env["PATH"]}'
export PATH="$PYBIN:$PATH"
{environment_exports}
env = options.environment.as_dictionary(env, executor=docker.environment_executor)
# 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
# check config python and pip are still on PATH
which_python = docker.call(['which', 'python'], env=env, capture_output=True).strip()
if PurePath(which_python) != python_bin / 'python':
print("cibuildwheel: python available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert python above it.", file=sys.stderr)
exit(1)
if [ ! -z {before_build} ]; then
sh -c {before_build}
fi
which_pip = docker.call(['which', 'pip'], env=env, capture_output=True).strip()
if PurePath(which_pip) != python_bin / 'pip':
print("cibuildwheel: pip available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert pip above it.", file=sys.stderr)
exit(1)
# 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)
if options.before_build:
before_build_prepared = prepare_command(options.before_build, project='/project', package=container_package_dir)
docker.call(['sh', '-c', before_build_prepared], env=env)
# 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)
temp_dir = PurePath('/tmp/cibuildwheel')
built_wheel_dir = temp_dir / 'built_wheel'
docker.call(['rm', '-rf', built_wheel_dir])
docker.call(['mkdir', '-p', built_wheel_dir])
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
docker.call([
'pip', 'wheel',
container_package_dir,
'-w', built_wheel_dir,
'--no-deps',
*get_build_verbosity_extra_flags(options.build_verbosity)
], env=env)
# run the tests in a subshell to keep that `activate`
# script from polluting the env
(
source "$venv_dir/bin/activate"
built_wheel = docker.glob(built_wheel_dir, '*.whl')[0]
echo "Running tests using `which python`"
repaired_wheel_dir = temp_dir / 'repaired_wheel'
docker.call(['rm', '-rf', repaired_wheel_dir])
docker.call(['mkdir', '-p', repaired_wheel_dir])
if [ ! -z {before_test} ]; then
sh -c {before_test}
fi
if built_wheel.name.endswith('none-any.whl') or not options.repair_command:
docker.call(['mv', built_wheel, repaired_wheel_dir])
else:
repair_command_prepared = prepare_command(options.repair_command, wheel=built_wheel, dest_dir=repaired_wheel_dir)
docker.call(['sh', '-c', repair_command_prepared], env=env)
# 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}
repaired_wheels = docker.glob(repaired_wheel_dir, '*.whl')
# Install any requirements to run the tests
if [ ! -z "{test_requires}" ]; then
pip install {test_requires}
fi
if options.test_command:
# set up a virtual environment to install and test from, to make sure
# there are no dependencies that were pulled in at build time.
docker.call(['pip', 'install', 'virtualenv', *dependency_constraint_flags], env=env)
venv_dir = PurePath(docker.call(['mktemp', '-d'], capture_output=True).strip()) / 'venv'
# 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
docker.call(['python', '-m', 'virtualenv', '--no-download', venv_dir], env=env)
# clean up
rm -rf "$venv_dir"
fi
virtualenv_env = env.copy()
virtualenv_env['PATH'] = f"{venv_dir / 'bin'}:{virtualenv_env['PATH']}"
# 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 '',
)
)
if options.before_test:
before_test_prepared = prepare_command(options.before_test, project='/project', package=container_package_dir)
docker.call(['sh', '-c', before_test_prepared], env=virtualenv_env)
# copy the output back into the host
call(['docker', 'cp',
container_name + ':/output/.',
os.path.abspath(options.output_dir)])
# Install the wheel we just built
# Note: If auditwheel produced two wheels, it's because the earlier produced wheel
# conforms to multiple manylinux standards. These multiple versions of the wheel are
# functionally the same, differing only in name, wheel metadata, and possibly include
# different external shared libraries. so it doesn't matter which one we run the tests on.
# Let's just pick the first one.
wheel_to_test = repaired_wheels[0]
docker.call(['pip', 'install', str(wheel_to_test) + options.test_extras], env=virtualenv_env)
# Install any requirements to run the tests
if options.test_requires:
docker.call(['pip', 'install', *options.test_requires], env=virtualenv_env)
# Run the tests from a different directory
test_command_prepared = prepare_command(options.test_command, project='/project', package=container_package_dir)
docker.call(['sh', '-c', test_command_prepared], cwd='/root', env=virtualenv_env)
# clean up test environment
docker.call(['rm', '-rf', venv_dir])
# move repaired wheels to output
docker.call(['mkdir', '-p', container_output_dir])
docker.call(['mv', *repaired_wheels, container_output_dir])
# copy the output back into the host
docker.copy_out(container_output_dir, options.output_dir)
except subprocess.CalledProcessError as error:
print(f'Command {error.cmd} failed with code {error.returncode}. {error.stdout}')
troubleshoot(options.package_dir, error)
exit(1)
finally:
# Still gets executed, even when 'exit(1)' gets called
call(['docker', 'rm', '--force', '-v', container_name])
def troubleshoot(package_dir, error):
def troubleshoot(package_dir: Path, error: Exception) -> None:
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(package_dir):
for name in files:
_, ext = os.path.splitext(name)
if ext == '.so':
so_files.append(os.path.join(root, name))
so_files = list(package_dir.glob('**/*.so'))
if so_files:
print(textwrap.dedent('''
@@ -297,5 +246,5 @@ def troubleshoot(package_dir, error):
'''))
print(' Files detected:')
print('\n'.join([' ' + f for f in so_files]))
print('\n'.join([f' {f}' for f in so_files]))
print('')
+88 -68
View File
@@ -4,37 +4,40 @@ import shutil
import subprocess
import sys
import tempfile
from collections import namedtuple
from glob import glob
from os import PathLike
from pathlib import Path
from typing import Dict, List, NamedTuple, Optional, Sequence, Union
from .util import (
download,
get_build_verbosity_extra_flags,
prepare_command,
get_pip_script,
BuildOptions
)
from .environment import ParsedEnvironment
from .util import (BuildOptions, BuildSelector, download,
get_build_verbosity_extra_flags, get_pip_script,
prepare_command)
def call(args, env=None, cwd=None, shell=False):
def call(args: Union[str, Sequence[Union[str, PathLike]]], env: Optional[Dict[str, str]] = None, cwd: Optional[str] = None, shell: bool = False) -> int:
# print the command executing for the logs
if shell:
print('+ %s' % args)
print(f'+ {args}')
else:
print('+ ' + ' '.join(shlex.quote(a) for a in args))
print('+ ' + ' '.join(shlex.quote(str(a)) for a in args))
return subprocess.check_call(args, env=env, cwd=cwd, shell=shell)
def get_python_configurations(build_selector):
PythonConfiguration = namedtuple('PythonConfiguration', ['version', 'identifier', 'url'])
class PythonConfiguration(NamedTuple):
version: str
identifier: str
url: str
def get_python_configurations(build_selector: BuildSelector) -> List[PythonConfiguration]:
python_configurations = [
# 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.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='3.7', identifier='cp37-macosx_x86_64', url='https://www.python.org/ftp/python/3.7.8/python-3.7.8-macosx10.9.pkg'),
PythonConfiguration(version='3.8', identifier='cp38-macosx_x86_64', url='https://www.python.org/ftp/python/3.8.4/python-3.8.4-macosx10.9.pkg'),
# 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'),
@@ -44,41 +47,41 @@ def get_python_configurations(build_selector):
return [c for c in python_configurations if build_selector(c.identifier)]
SYMLINKS_DIR = '/tmp/cibw_bin'
SYMLINKS_DIR = Path('/tmp/cibw_bin')
def make_symlinks(installation_bin_path, python_executable, pip_executable):
assert os.path.exists(os.path.join(installation_bin_path, python_executable))
def make_symlinks(installation_bin_path: Path, python_executable: str, pip_executable: str) -> None:
assert (installation_bin_path / python_executable).exists()
# Python bin folders on Mac don't symlink `python3` to `python`, and neither
# does PyPy for `pypy` or `pypy3`, so we do that so `python` and `pip` always
# point to the active configuration.
if os.path.exists(SYMLINKS_DIR):
if SYMLINKS_DIR.exists():
shutil.rmtree(SYMLINKS_DIR)
os.makedirs(SYMLINKS_DIR)
SYMLINKS_DIR.mkdir(parents=True)
os.symlink(os.path.join(installation_bin_path, python_executable), os.path.join(SYMLINKS_DIR, 'python'))
os.symlink(os.path.join(installation_bin_path, python_executable + '-config'), os.path.join(SYMLINKS_DIR, 'python-config'))
os.symlink(os.path.join(installation_bin_path, pip_executable), os.path.join(SYMLINKS_DIR, 'pip'))
(SYMLINKS_DIR / 'python').symlink_to(installation_bin_path / python_executable)
(SYMLINKS_DIR / 'python-config').symlink_to(installation_bin_path / (python_executable + '-config'))
(SYMLINKS_DIR / 'pip').symlink_to(installation_bin_path / pip_executable)
def install_cpython(version, url):
def install_cpython(version: str, url: str) -> Path:
installed_system_packages = subprocess.check_output(['pkgutil', '--pkgs'], universal_newlines=True).splitlines()
# if this version of python isn't installed, get it from python.org and install
python_package_identifier = 'org.python.Python.PythonFramework-{}'.format(version)
python_package_identifier = f'org.python.Python.PythonFramework-{version}'
if python_package_identifier not in installed_system_packages:
# download the pkg
download(url, '/tmp/Python.pkg')
download(url, Path('/tmp/Python.pkg'))
# install
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.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'])
open_ssl_patch_url = f'https://github.com/mayeut/patch-macos-python-openssl/releases/download/v1.0.2u/patch-macos-python-{version}-openssl-v1.0.2u.tar.gz'
download(open_ssl_patch_url, Path('/tmp/python-patch.tar.gz'))
call(['sudo', 'tar', '-C', f'/Library/Frameworks/Python.framework/Versions/{version}/', '-xmf', '/tmp/python-patch.tar.gz'])
installation_bin_path = '/Library/Frameworks/Python.framework/Versions/{}/bin'.format(version)
installation_bin_path = Path(f'/Library/Frameworks/Python.framework/Versions/{version}/bin')
python_executable = 'python3' if version[0] == '3' else 'python'
pip_executable = 'pip3' if version[0] == '3' else 'pip'
make_symlinks(installation_bin_path, python_executable, pip_executable)
@@ -86,16 +89,18 @@ def install_cpython(version, url):
return installation_bin_path
def install_pypy(version, url):
def install_pypy(version: str, url: str) -> Path:
pypy_tar_bz2 = url.rsplit('/', 1)[-1]
assert pypy_tar_bz2.endswith(".tar.bz2")
pypy_base_filename = os.path.splitext(os.path.splitext(pypy_tar_bz2)[0])[0]
installation_path = os.path.join('/tmp', pypy_base_filename)
if not os.path.exists(installation_path):
download(url, os.path.join("/tmp", pypy_tar_bz2))
call(['tar', '-C', '/tmp', '-xf', os.path.join("/tmp", pypy_tar_bz2)])
extension = ".tar.bz2"
assert pypy_tar_bz2.endswith(extension)
pypy_base_filename = pypy_tar_bz2[:-len(extension)]
installation_path = Path('/tmp') / pypy_base_filename
if not installation_path.exists():
downloaded_tar_bz2 = Path("/tmp") / pypy_tar_bz2
download(url, downloaded_tar_bz2)
call(['tar', '-C', '/tmp', '-xf', downloaded_tar_bz2])
installation_bin_path = os.path.join(installation_path, 'bin')
installation_bin_path = installation_path / 'bin'
python_executable = 'pypy3' if version[0] == '3' else 'pypy'
pip_executable = 'pip3' if version[0] == '3' else 'pip'
make_symlinks(installation_bin_path, python_executable, pip_executable)
@@ -103,7 +108,9 @@ def install_pypy(version, url):
return installation_bin_path
def setup_python(python_configuration, dependency_constraint_flags, environment):
def setup_python(python_configuration: PythonConfiguration,
dependency_constraint_flags: Sequence[Union[str, PathLike]],
environment: ParsedEnvironment) -> Dict[str, str]:
if python_configuration.identifier.startswith('cp'):
installation_bin_path = install_cpython(python_configuration.version, python_configuration.url)
elif python_configuration.identifier.startswith('pp'):
@@ -113,8 +120,8 @@ def setup_python(python_configuration, dependency_constraint_flags, environment)
env = os.environ.copy()
env['PATH'] = os.pathsep.join([
SYMLINKS_DIR,
installation_bin_path,
str(SYMLINKS_DIR),
str(installation_bin_path),
env['PATH'],
])
@@ -136,15 +143,15 @@ def setup_python(python_configuration, dependency_constraint_flags, environment)
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(['python', get_pip_script, *dependency_constraint_flags], env=env, cwd="/tmp")
assert (installation_bin_path / 'pip').exists()
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)
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
@@ -161,15 +168,20 @@ def setup_python(python_configuration, dependency_constraint_flags, environment)
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')
def build(options: BuildOptions) -> None:
temp_dir = Path(tempfile.mkdtemp(prefix='cibuildwheel'))
built_wheel_dir = temp_dir / 'built_wheel'
repaired_wheel_dir = temp_dir / 'repaired_wheel'
if options.before_all:
env = options.environment.as_dictionary(prev_environment=os.environ)
before_all_prepared = prepare_command(options.before_all, project='.', package=options.package_dir)
call([before_all_prepared], shell=True, env=env)
python_configurations = get_python_configurations(options.build_selector)
for config in python_configurations:
dependency_constraint_flags = []
dependency_constraint_flags: Sequence[Union[str, PathLike]] = []
if options.dependency_constraints:
dependency_constraint_flags = [
'-c', options.dependency_constraints.get_for_python_version(config.version)
@@ -183,29 +195,39 @@ def build(options: BuildOptions):
call(before_build_prepared, env=env, shell=True)
# build the wheel
if os.path.exists(built_wheel_dir):
if built_wheel_dir.exists():
shutil.rmtree(built_wheel_dir)
os.makedirs(built_wheel_dir)
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]
built_wheel_dir.mkdir(parents=True)
# Path.resolve() is needed. Without it pip wheel may try to fetch package from pypi.org
# see https://github.com/joerick/cibuildwheel/pull/369
call([
'pip', 'wheel',
options.package_dir.resolve(),
'-w', built_wheel_dir,
'--no-deps',
*get_build_verbosity_extra_flags(options.build_verbosity)
], env=env)
built_wheel = next(built_wheel_dir.glob('*.whl'))
# repair the wheel
if os.path.exists(repaired_wheel_dir):
if repaired_wheel_dir.exists():
shutil.rmtree(repaired_wheel_dir)
os.makedirs(repaired_wheel_dir)
if built_wheel.endswith('none-any.whl') or not options.repair_command:
repaired_wheel_dir.mkdir(parents=True)
if built_wheel.name.endswith('none-any.whl') or not options.repair_command:
# pure Python wheel or empty repair command
shutil.move(built_wheel, repaired_wheel_dir)
shutil.move(str(built_wheel), repaired_wheel_dir)
else:
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]
repaired_wheel = next(repaired_wheel_dir.glob('*.whl'))
if options.test_command:
# set up a virtual environment to install and test from, to make sure
# there are no dependencies that were pulled in at build time.
call(['pip', 'install', 'virtualenv'] + dependency_constraint_flags, env=env)
venv_dir = tempfile.mkdtemp()
call(['pip', 'install', 'virtualenv', *dependency_constraint_flags], env=env)
venv_dir = Path(tempfile.mkdtemp())
# Use --no-download to ensure determinism by using seed libraries
# built into virtualenv
@@ -213,10 +235,9 @@ def build(options: BuildOptions):
virtualenv_env = env.copy()
virtualenv_env['PATH'] = os.pathsep.join([
os.path.join(venv_dir, 'bin'),
str(venv_dir / 'bin'),
virtualenv_env['PATH'],
])
virtualenv_env["__CIBW_VIRTUALENV_PATH__"] = venv_dir
# check that we are using the Python from the virtual environment
call(['which', 'python'], env=virtualenv_env)
@@ -226,7 +247,7 @@ def build(options: BuildOptions):
call(before_test_prepared, env=virtualenv_env, shell=True)
# install the wheel
call(['pip', 'install', repaired_wheel + options.test_extras], env=virtualenv_env)
call(['pip', 'install', str(repaired_wheel) + options.test_extras], env=virtualenv_env)
# test the wheel
if options.test_requires:
@@ -237,8 +258,8 @@ def build(options: BuildOptions):
# and not the repo code)
test_command_prepared = prepare_command(
options.test_command,
project=os.path.abspath('.'),
package=os.path.abspath(options.package_dir)
project=Path('.').resolve(),
package=options.package_dir.resolve()
)
call(test_command_prepared, cwd=os.environ['HOME'], env=virtualenv_env, shell=True)
@@ -246,5 +267,4 @@ def build(options: BuildOptions):
shutil.rmtree(venv_dir)
# we're all done here; move it to output (overwrite existing)
dst = os.path.join(options.output_dir, os.path.basename(repaired_wheel))
shutil.move(repaired_wheel, dst)
shutil.move(str(repaired_wheel), options.output_dir)
@@ -4,7 +4,7 @@
#
# bin/update_constraints.py
#
appdirs==1.4.3 # via virtualenv
appdirs==1.4.4 # 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
@@ -15,12 +15,12 @@ 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
six==1.15.0 # via pathlib2, virtualenv
typing==3.7.4.1 # via importlib-resources
virtualenv==20.0.18 # via -r cibuildwheel/resources/constraints.in
virtualenv==20.0.21 # 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
pip==20.1.1 # via -r cibuildwheel/resources/constraints.in
setuptools==44.1.0 # via -r cibuildwheel/resources/constraints.in
@@ -4,17 +4,17 @@
#
# bin/update_constraints.py
#
appdirs==1.4.3 # via virtualenv
appdirs==1.4.4 # 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
six==1.15.0 # via virtualenv
virtualenv==20.0.21 # 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
pip==20.1.1 # via -r cibuildwheel/resources/constraints.in
setuptools==46.4.0 # via -r cibuildwheel/resources/constraints.in
@@ -4,17 +4,17 @@
#
# bin/update_constraints.py
#
appdirs==1.4.3 # via virtualenv
appdirs==1.4.4 # 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
six==1.15.0 # via virtualenv
virtualenv==20.0.21 # 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
pip==20.1.1 # via -r cibuildwheel/resources/constraints.in
setuptools==46.4.0 # via -r cibuildwheel/resources/constraints.in
+5 -5
View File
@@ -4,16 +4,16 @@
#
# bin/update_constraints.py
#
appdirs==1.4.3 # via virtualenv
appdirs==1.4.4 # 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
six==1.15.0 # via virtualenv
virtualenv==20.0.21 # 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
pip==20.1.1 # via -r cibuildwheel/resources/constraints.in
setuptools==46.4.0 # via -r cibuildwheel/resources/constraints.in
@@ -1,22 +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
manylinux1 = quay.io/pypa/manylinux1_x86_64:2020-05-25-b56702d
manylinux2010 = quay.io/pypa/manylinux2010_x86_64:2020-05-20-c7bbd11
manylinux2014 = quay.io/pypa/manylinux2014_x86_64:2020-05-17-2f8ac3b
[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
manylinux1 = quay.io/pypa/manylinux1_i686:2020-05-25-b56702d
manylinux2010 = quay.io/pypa/manylinux2010_i686:2020-05-20-c7bbd11
manylinux2014 = quay.io/pypa/manylinux2014_i686:2020-05-17-2f8ac3b
[pypy_x86_64]
manylinux2010 = pypywheels/manylinux2010-pypy_x86_64:2020-04-25-eb2cdff
[aarch64]
manylinux2014 = quay.io/pypa/manylinux2014_aarch64:2020-05-01-b37d76b
manylinux2014 = quay.io/pypa/manylinux2014_aarch64:2020-05-17-2f8ac3b
[ppc64le]
manylinux2014 = quay.io/pypa/manylinux2014_ppc64le:2020-05-01-b37d76b
manylinux2014 = quay.io/pypa/manylinux2014_ppc64le:2020-05-17-2f8ac3b
[s390x]
manylinux2014 = quay.io/pypa/manylinux2014_s390x:2020-05-01-b37d76b
manylinux2014 = quay.io/pypa/manylinux2014_s390x:2020-05-17-2f8ac3b
-7
View File
@@ -1,7 +0,0 @@
--- a/lib_pypy/_sysconfigdata.py Tue Jan 28 22:54:59 2020 +0200
+++ b/lib_pypy/_sysconfigdata.py Wed Jan 29 19:21:23 2020 +0200
@@ -47,4 +47,5 @@
build_time_vars['CC'] += ' -arch %s' % (arch,)
if "CXX" in build_time_vars:
build_time_vars['CXX'] += ' -arch %s' % (arch,)
+ build_time_vars['MACOSX_DEPLOYMENT_TARGET'] = '10.7'
+49 -52
View File
@@ -1,13 +1,15 @@
import os
import urllib.request
from fnmatch import fnmatch
from pathlib import Path
from time import sleep
from typing import NamedTuple, List, Optional, Dict
from typing import Dict, List, NamedTuple, Optional, Union
from .environment import ParsedEnvironment
def prepare_command(command, **kwargs):
def prepare_command(command: str, **kwargs: Union[str, os.PathLike]) -> str:
'''
Preprocesses a command by expanding variables like {python}.
@@ -17,7 +19,7 @@ def prepare_command(command, **kwargs):
return command.format(python='python', pip='pip', **kwargs)
def get_build_verbosity_extra_flags(level):
def get_build_verbosity_extra_flags(level: int) -> List[str]:
if level > 0:
return ['-' + level * 'v']
elif level < 0:
@@ -27,41 +29,41 @@ def get_build_verbosity_extra_flags(level):
class BuildSelector:
def __init__(self, build_config, skip_config):
def __init__(self, build_config: str, skip_config: str):
self.build_patterns = build_config.split()
self.skip_patterns = skip_config.split()
def __call__(self, build_id):
def match_any(patterns):
def __call__(self, build_id: str) -> bool:
def match_any(patterns: List[str]) -> bool:
return any(fnmatch(build_id, pattern) for pattern in patterns)
return match_any(self.build_patterns) and not match_any(self.skip_patterns)
def __repr__(self):
return 'BuildSelector({!r} - {!r})'.format(' '.join(self.build_patterns), ' '.join(self.skip_patterns))
def __repr__(self) -> str:
return f'BuildSelector({" ".join(self.build_patterns)!r} - {" ".join(self.skip_patterns)!r})'
# Taken from https://stackoverflow.com/a/107717
class Unbuffered:
def __init__(self, stream):
def __init__(self, stream): # type: ignore
self.stream = stream
def write(self, data):
def write(self, data): # type: ignore
self.stream.write(data)
self.stream.flush()
def writelines(self, datas):
def writelines(self, datas): # type: ignore
self.stream.writelines(datas)
self.stream.flush()
def __getattr__(self, attr):
def __getattr__(self, attr): # type: ignore
return getattr(self.stream, attr)
def download(url, dest):
print('+ Download ' + url + ' to ' + dest)
dest_dir = os.path.dirname(dest)
if not os.path.exists(dest_dir):
os.makedirs(dest_dir)
def download(url: str, dest: Path) -> None:
print(f'+ Download {url} to {dest}')
dest_dir = dest.parent
if not dest_dir.exists():
dest_dir.mkdir(parents=True)
repeat_num = 3
for i in range(repeat_num):
@@ -75,57 +77,52 @@ def download(url, dest):
break
try:
with open(dest, 'wb') as file:
file.write(response.read())
dest.write_bytes(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)
def __init__(self, base_file_path: Path):
assert base_file_path.exists()
self.base_file_path = base_file_path.resolve()
@classmethod
def with_defaults(cls):
return cls(
base_file_path=os.path.join(os.path.dirname(__file__), 'resources', 'constraints.txt')
@staticmethod
def with_defaults() -> 'DependencyConstraints':
return DependencyConstraints(
base_file_path=resources_dir / 'constraints.txt'
)
def get_for_python_version(self, version):
def get_for_python_version(self, version: str) -> Path:
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):
specific_stem = self.base_file_path.stem + f'-python{version_parts[0]}{version_parts[1]}'
specific_name = specific_stem + self.base_file_path.suffix
specific_file_path = self.base_file_path.with_name(specific_name)
if specific_file_path.exists():
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]]),
])
class BuildOptions(NamedTuple):
package_dir: Path
output_dir: Path
build_selector: BuildSelector
environment: ParsedEnvironment
before_all: str
before_build: Optional[str]
repair_command: str
manylinux_images: Optional[Dict[str, str]]
dependency_constraints: Optional[DependencyConstraints]
test_command: Optional[str]
before_test: Optional[str]
test_requires: List[str]
test_extras: str
build_verbosity: int
"""
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')
resources_dir = Path(__file__).resolve().parent / 'resources'
get_pip_script = resources_dir / 'get-pip.py'
+136 -86
View File
@@ -3,37 +3,49 @@ import shutil
import subprocess
import sys
import tempfile
from collections import namedtuple
from glob import glob
from os import PathLike
from pathlib import Path
from typing import Dict, List, NamedTuple, Optional, Sequence, Union
from zipfile import ZipFile
import toml
from .util import (
download,
get_build_verbosity_extra_flags,
prepare_command,
get_pip_script,
BuildOptions
)
from .environment import ParsedEnvironment
from .util import (BuildOptions, BuildSelector, download,
get_build_verbosity_extra_flags, get_pip_script,
prepare_command)
IS_RUNNING_ON_AZURE = os.path.exists('C:\\hostedtoolcache')
IS_RUNNING_ON_AZURE = Path('C:\\hostedtoolcache').exists()
IS_RUNNING_ON_TRAVIS = os.environ.get('TRAVIS_OS_NAME') == 'windows'
def shell(args, env=None, cwd=None):
print('+ ' + ' '.join(args))
return subprocess.check_call(' '.join(args), env=env, cwd=cwd, shell=True)
def call(args: Sequence[Union[str, PathLike]], env: Optional[Dict[str, str]] = None,
cwd: Optional[str] = None) -> None:
print('+ ' + ' '.join(str(a) for a in args))
# we use shell=True here, even though we don't need a shell due to a bug
# https://bugs.python.org/issue8557
subprocess.check_call([str(a) for a in args], env=env, cwd=cwd, shell=True)
def get_nuget_args(version, arch):
def shell(command: str, env: Optional[Dict[str, str]] = None, cwd: Optional[str] = None) -> None:
print(f'+ {command}')
subprocess.check_call(command, env=env, cwd=cwd, shell=True)
def get_nuget_args(version: str, arch: str) -> List[str]:
python_name = 'python' if version[0] == '3' else 'python2'
if arch == '32':
python_name = python_name + 'x86'
return [python_name, '-Version', version, '-OutputDirectory', 'C:\\cibw\\python']
def get_python_configurations(build_selector):
PythonConfiguration = namedtuple('PythonConfiguration', ['version', 'arch', 'identifier', 'url'])
class PythonConfiguration(NamedTuple):
version: str
arch: str
identifier: str
url: Optional[str]
def get_python_configurations(build_selector: BuildSelector) -> List[PythonConfiguration]:
python_configurations = [
# CPython
PythonConfiguration(version='2.7.18', arch='32', identifier='cp27-win32', url=None),
@@ -42,10 +54,10 @@ def get_python_configurations(build_selector):
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.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='3.7.8', arch='32', identifier='cp37-win32', url=None),
PythonConfiguration(version='3.7.8', arch='64', identifier='cp37-win_amd64', url=None),
PythonConfiguration(version='3.8.4', arch='32', identifier='cp38-win32', url=None),
PythonConfiguration(version='3.8.4', arch='64', identifier='cp38-win_amd64', url=None),
# 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'),
@@ -62,97 +74,130 @@ def get_python_configurations(build_selector):
return python_configurations
def extract_zip(zip_src, dest):
def extract_zip(zip_src: Path, dest: Path) -> None:
with ZipFile(zip_src) as zip:
zip.extractall(dest)
def install_cpython(version, arch, nuget):
def install_cpython(version: str, arch: str, nuget: Path) -> Path:
nuget_args = get_nuget_args(version, arch)
installation_path = os.path.join(nuget_args[-1], nuget_args[0] + '.' + version, 'tools')
shell([nuget, 'install'] + nuget_args)
installation_path = Path(nuget_args[-1]) / (nuget_args[0] + '.' + version) / 'tools'
call([nuget, 'install', *nuget_args])
return installation_path
def install_pypy(version, arch, url):
def install_pypy(version: str, arch: str, url: str) -> Path:
assert arch == '32'
# Inside the PyPy zip file is a directory with the same name
zip_filename = url.rsplit('/', 1)[-1]
installation_path = os.path.join('C:\\cibw', os.path.splitext(zip_filename)[0])
if not os.path.exists(installation_path):
pypy_zip = os.path.join('C:\\cibw', zip_filename)
extension = ".zip"
assert zip_filename.endswith(extension)
installation_path = Path('C:\\cibw') / zip_filename[:-len(extension)]
if not installation_path.exists():
pypy_zip = Path('C:\\cibw') / zip_filename
download(url, pypy_zip)
# Extract to the parent directory because the zip file still contains a directory
extract_zip(pypy_zip, os.path.dirname(installation_path))
extract_zip(pypy_zip, installation_path.parent)
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)])
(installation_path / 'python.exe').symlink_to(installation_path / pypy_exe)
return installation_path
def setup_python(python_configuration, dependency_constraint_flags, environment):
nuget = 'C:\\cibw\\nuget.exe'
if not os.path.exists(nuget):
def setup_python(python_configuration: PythonConfiguration, dependency_constraint_flags: Sequence[Union[str, PathLike]], environment: ParsedEnvironment) -> Dict[str, str]:
nuget = Path('C:\\cibw\\nuget.exe')
if not nuget.exists():
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'):
assert python_configuration.url is not None
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'))
assert (installation_path / 'python.exe').exists()
# 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'),
str(installation_path),
str(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)
call(['where', 'python'], env=env)
call(['python', '--version'], env=env)
call(['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'):
if where_python != str(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'))
if not (installation_path / 'Scripts' / 'pip.exe').exists():
call(['python', get_pip_script, *dependency_constraint_flags], env=env, cwd="C:\\cibw")
assert (installation_path / 'Scripts' / 'pip.exe').exists()
where_pip = subprocess.check_output(['where', 'pip'], env=env, universal_newlines=True).splitlines()[0].strip()
if where_pip.strip() != os.path.join(installation_path, 'Scripts', 'pip.exe'):
if where_pip.strip() != str(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)
call(['python', '-m', 'pip', 'install', '--upgrade', 'pip', *dependency_constraint_flags], env=env)
call(['pip', '--version'], env=env)
call(['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')
def pep_518_cp35_workaround(package_dir: Path, env: Dict[str, str]) -> None:
"""
Python 3.5 PEP 518 hack (see https://github.com/pypa/pip/issues/8392#issuecomment-639563494)
Basically, nuget's Python is an embedded Python distribution, which is not supported by pip.
Before version 3.6, there was no way to disable the "embedded" behavior, including the ignoring
of environment variables, including the ones pip uses to setup PEP 518 builds.
The fix here is as suggested in that issue; we manually setup the PEP 518 requirements. Since we
are in a fresh environment (except for pinned cibuildweel dependencies), the build is already
mostly "isolated".
"""
pyproject_path = package_dir / 'pyproject.toml'
if pyproject_path.exists():
data = toml.load(pyproject_path)
requirements = (
data['build-system'].get('requires', [])
if 'build-system' in data
else []
)
if requirements:
shell(['pip', 'install'] + requirements, env=env)
def build(options: BuildOptions) -> None:
temp_dir = Path(tempfile.mkdtemp(prefix='cibuildwheel'))
built_wheel_dir = temp_dir / 'built_wheel'
repaired_wheel_dir = temp_dir / 'repaired_wheel'
# install nuget as best way to provide python
nuget = 'C:\\cibw\\nuget.exe'
nuget = Path('C:\\cibw\\nuget.exe')
download('https://dist.nuget.org/win-x86-commandline/latest/nuget.exe', nuget)
if options.before_all:
env = options.environment.as_dictionary(prev_environment=os.environ)
before_all_prepared = prepare_command(options.before_all, project='.', package=options.package_dir)
shell(before_all_prepared, env=env)
python_configurations = get_python_configurations(options.build_selector)
for config in python_configurations:
dependency_constraint_flags = []
dependency_constraint_flags: Sequence[Union[str, PathLike]] = []
if options.dependency_constraints:
dependency_constraint_flags = [
'-c', options.dependency_constraints.get_for_python_version(config.version)
@@ -164,51 +209,59 @@ def build(options: BuildOptions):
# run the before_build command
if options.before_build:
before_build_prepared = prepare_command(options.before_build, project='.', package=options.package_dir)
shell([before_build_prepared], env=env)
shell(before_build_prepared, env=env)
# activate the PEP 518 patch if on Windows Python 3.5
# (will only have an effect if PEP 517 builds are used):
if config.version.startswith('3.5'):
pep_518_cp35_workaround(options.package_dir, env)
# build the wheel
if os.path.exists(built_wheel_dir):
if built_wheel_dir.exists():
shutil.rmtree(built_wheel_dir)
os.makedirs(built_wheel_dir)
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]
built_wheel_dir.mkdir(parents=True)
# Path.resolve() is needed. Without it pip wheel may try to fetch package from pypi.org
# see https://github.com/joerick/cibuildwheel/pull/369
call([
'pip', 'wheel',
options.package_dir.resolve(),
'-w', built_wheel_dir,
'--no-deps',
*get_build_verbosity_extra_flags(options.build_verbosity)
], env=env)
built_wheel = next(built_wheel_dir.glob('*.whl'))
# repair the wheel
if os.path.exists(repaired_wheel_dir):
if repaired_wheel_dir.exists():
shutil.rmtree(repaired_wheel_dir)
os.makedirs(repaired_wheel_dir)
if built_wheel.endswith('none-any.whl') or not options.repair_command:
repaired_wheel_dir.mkdir(parents=True)
if built_wheel.name.endswith('none-any.whl') or not options.repair_command:
# pure Python wheel or empty repair command
shutil.move(built_wheel, repaired_wheel_dir)
shutil.move(str(built_wheel), repaired_wheel_dir)
else:
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]
shell(repair_command_prepared, env=env)
repaired_wheel = next(repaired_wheel_dir.glob('*.whl'))
if options.test_command:
# set up a virtual environment to install and test from, to make sure
# there are no dependencies that were pulled in at build time.
shell(['pip', 'install', 'virtualenv'] + dependency_constraint_flags, env=env)
venv_dir = tempfile.mkdtemp()
call(['pip', 'install', 'virtualenv', *dependency_constraint_flags], env=env)
venv_dir = Path(tempfile.mkdtemp())
# Use --no-download to ensure determinism by using seed libraries
# built into virtualenv
shell(['python', '-m', 'virtualenv', '--no-download', venv_dir], env=env)
call(['python', '-m', 'virtualenv', '--no-download', venv_dir], env=env)
virtualenv_env = env.copy()
venv_script_path = os.path.join(venv_dir, 'Scripts')
if os.path.exists(os.path.join(venv_dir, 'bin')):
# pypy2.7 bugfix
venv_script_path = os.pathsep.join([venv_script_path, os.path.join(venv_dir, 'bin')])
virtualenv_env['PATH'] = os.pathsep.join([
venv_script_path,
str(venv_dir / 'Scripts'),
virtualenv_env['PATH'],
])
virtualenv_env["__CIBW_VIRTUALENV_PATH__"] = venv_dir
# check that we are using the Python from the virtual environment
shell(['which', 'python'], env=virtualenv_env)
call(['which', 'python'], env=virtualenv_env)
if options.before_test:
before_test_prepared = prepare_command(
@@ -216,30 +269,27 @@ def build(options: BuildOptions):
project='.',
package=options.package_dir
)
shell([before_test_prepared], env=virtualenv_env)
shell(before_test_prepared, env=virtualenv_env)
# install the wheel
shell(['pip', 'install', repaired_wheel + options.test_extras], env=virtualenv_env)
call(['pip', 'install', str(repaired_wheel) + options.test_extras], env=virtualenv_env)
# test the wheel
if options.test_requires:
shell(['pip', 'install'] + options.test_requires, env=virtualenv_env)
call(['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(
options.test_command,
project=os.path.abspath('.'),
package=os.path.abspath(options.package_dir)
project=Path('.').resolve(),
package=options.package_dir.resolve()
)
shell([test_command_prepared], cwd='c:\\', env=virtualenv_env)
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(options.output_dir, os.path.basename(repaired_wheel))
if os.path.isfile(dst):
os.remove(dst)
shutil.move(repaired_wheel, dst)
shutil.move(str(repaired_wheel), options.output_dir)
+36 -2
View File
@@ -30,6 +30,40 @@ For more details see https://en.cppreference.com/w/cpp/compiler_support, https:/
## Windows and Python 2.7
Visual C++ for Python 2.7 does not support modern standards of C++. When building on Appveyor, you will need to either use the "Visual Studio 2017" or "Visual Studio 2019" image, but Python 2.7 is not supported on these images - skip it by setting `CIBW_SKIP=cp27-win*`.
Visual C++ for Python 2.7 does not support modern C++ standards (i.e., C++11 and later). When building on Appveyor, you will need to either use the "Visual Studio 2017" or "Visual Studio 2019" image, but Python 2.7 is not supported on these images - skip it by setting `CIBW_SKIP=cp27-win*`.
There is an optional workaround for this, though: the pybind11 project argues and shows that it is [possible to compile Python 2.7 extension with a newer compiler](https://pybind11.readthedocs.io/en/stable/faq.html#working-with-ancient-visual-studio-2008-builds-on-windows) and has an example project showing how to do this: https://github.com/pybind/python_example. The main catch is that a user might need to install a newer "Microsoft Visual C++ Redistributable", since the newer C++ standard libraries are not included by default with the Python 2.7 installation.
There is an optional workaround for this, though: the pybind11 project argues and shows that it is [possible to compile Python 2.7 extension with a newer compiler](https://pybind11.readthedocs.io/en/stable/faq.html#working-with-ancient-visual-studio-2008-builds-on-windows) and has an example project showing how to do this: https://github.com/pybind/python_example. The main catch is that a user might need to install [a newer "Microsoft Visual C++ Redistributable"](https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads), since the newer C++ standard library's binaries are not included by default with the Python 2.7 installation.
Forcing `distutils` or `setuptools` to use a more recent version of MSVC that supports modern C++ can be done in the following way:
1. Set the environment variables `DISTUTILS_USE_SDK=1` and `MSSdk=1`. These two environment variables will tell `distutils`/`setuptools` to not search and set up a build environment that uses Visual C++ for Python 2.7 (aka. MSVC 9).
2. Set up the build Visual Studio build environment you want to use, making sure that e.g. `PATH` contains `cl`, `link`, etc.
- Usually, this can be done through `vcvarsall.bat x86` or `vcvarsall.bat x64`. The exact location of this file depends on the installation, but the default path for VS 2019 Community (e.g. used in AppVeyor's `Visual Studio 2019` image) is `C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvarsall.bat`. **Note**: `vcvarsall.bat` changes the environment variables, so this cannot be run in a subprocess/subshell and consequently running `vsvarsall.bat` in `CIBW_BEFORE_BUILD` does not have any effect.
- In Azure Pipelines, [a `VSBuild` task is available](https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/build/visual-studio-build)
- In GitHub Actions, the default shell is powershell, so you'll need to use `shell: cmd` to source a `.bat` file, and the directory is `"C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat"`. Or you can use an action, such as the [`ilammy/msvc-dev-cmd@v1`](https://github.com/ilammy/msvc-dev-cmd) action to setup the environment (see example below).
3. Next, call `cibuildwheel`. Unfortunately, MSVC has separate toolchains for compiling 32-bit and 64-bit, so you will need to run `cibuildwheel` twice: once with `CIBW_BUILD=*-win32` after setting up the `x86` build environment, and once with `CIBW_BUILD=*-win_amd64` in a `x64` environment (see previous step).
**GitHub Action example with ilammy/msvc-dev-cmd**:
```yaml
- uses: ilammy/msvc-dev-cmd@v1
- name: Build 64-bit wheel
run: python -m cibuildwheel --output-dir wheelhouse
env:
CIBW_BUILD: cp27-win_amd64
DISTUTILS_USE_SDK: 1
MSSdk: 1
- uses: ilammy/msvc-dev-cmd@v1
with:
arch: x86
- name: Build 32-bit wheel
run: python -m cibuildwheel --output-dir wheelhouse
env:
CIBW_BUILD: cp27-win32
DISTUTILS_USE_SDK: 1
MSSdk: 1
```
+1 -1
View File
@@ -33,4 +33,4 @@ Obviously, manual steps are for chumps, so we can automate this a little by usin
If you don't need much control over the release of a package, you can set up cibuildwheel to deliver the wheels straight to PyPI. This doesn't require anycloud storage to work - you just need to bump the version and tag it.
Check out [this example repo](https://github.com/joerick/cibuildwheel-autopypi-example) for instructions on how to set this up.
[`examples/travis-ci-deploy.yml`](https://github.com/joerick/cibuildwheel/blob/master/examples/travis-ci-deploy.yml) and [`examples/github-deploy.yml`](https://github.com/joerick/cibuildwheel/blob/master/examples/github-deploy.yml) are example configurations that automatocially upload wheels to PyPI. Also check out [this example repo](https://github.com/joerick/cibuildwheel-autopypi-example) for more detailed instructions on how to set this up.
-1
View File
@@ -2,4 +2,3 @@
$('.rst-content pre')
.prev('blockquote')
.addClass('code-block-filename');
@@ -1,7 +1,6 @@
import cgi
import io
import os
import re
from pathlib import Path
import mkdocs
@@ -41,13 +40,12 @@ class ImportMarkdownPlugin(mkdocs.plugins.BasePlugin):
def found_include_tag(match):
filename = match.group('filename')
file_path_abs = os.path.join(os.path.dirname(page_src_path), filename)
file_path_abs = Path(page_src_path).parent / filename
if not os.path.exists(file_path_abs):
if not file_path_abs.exists():
raise ValueError('file not found', filename)
with io.open(file_path_abs, encoding='utf8') as f:
text_to_include = f.read()
text_to_include = file_path_abs.read_text(encoding='utf8')
# Allow good practice of having a final newline in the file
if text_to_include.endswith('\n'):
@@ -60,13 +58,12 @@ class ImportMarkdownPlugin(mkdocs.plugins.BasePlugin):
start = match.group('start')
end = match.group('end')
file_path_abs = os.path.join(os.path.dirname(page_src_path), filename)
file_path_abs = Path(page_src_path).parent / filename
if not os.path.exists(file_path_abs):
if not file_path_abs.exists():
raise ValueError('file not found', filename)
with io.open(file_path_abs, encoding='utf8') as f:
text_to_include = f.read()
text_to_include = file_path_abs.read_text(encoding='utf8')
if start:
_, _, text_to_include = text_to_include.partition(start)
+54 -30
View File
@@ -52,7 +52,6 @@ env:
## Build selection
### `CIBW_PLATFORM` {: #platform}
> Override the auto-detected target platform
@@ -67,6 +66,7 @@ For `linux` you need Docker running, on macOS or Linux. For `macos`, you need a
This option can also be set using the command-line option `--platform`.
### `CIBW_BUILD`, `CIBW_SKIP` {: #build-skip}
> Choose the Python versions to build
@@ -155,7 +155,6 @@ CIBW_SKIP: pp*
## Build customization
### `CIBW_ENVIRONMENT` {: #environment}
> Set environment variables needed during the build
@@ -191,6 +190,28 @@ CIBW_ENVIRONMENT: "BUILD_TIME=$(date) SAMPLE_TEXT=\"sample text\""
!!! note
`cibuildwheel` always defines the environment variable `CIBUILDWHEEL=1`. This can be useful for [building wheels with optional extensions](faq.md#building-packages-with-optional-c-extensions).
### `CIBW_BEFORE_ALL` {: #before-all}
> Execute a shell command on the build system before any wheels are built.
Shell command to prepare a common part of the project (e.g. build or install libraries which does not depend on the specific version of Python).
This option is very useful for the Linux build, where builds take place in isolated Docker containers managed by cibuildwheel. This command will run inside the container before the wheel builds start. Note, if you're building both x86_64 and i686 wheels (the default), your build uses two different Docker images. In that case, this command will execute twice - once per build container.
The placeholder `{package}` can be used here; it will be replaced by the path to the package being built by `cibuildwheel`.
On Windows and macOS, the version of Python available inside `CIBW_BEFORE_ALL` is whatever is available on the host machine. On Linux, a modern Python version is available on PATH.
Platform-specific variants also available:<br/>
`CIBW_BEFORE_ALL_MACOS` | `CIBW_BEFORE_ALL_WINDOWS` | `CIBW_BEFORE_ALL_LINUX`
#### Examples
```yaml
# build third party library
CIBW_BEFORE_ALL: make -C third_party_lib
# install system library
CIBW_BEFORE_ALL_LINUX: yum install -y libffi-dev
```
### `CIBW_BEFORE_BUILD` {: #before-build}
> Execute a shell command preparing each wheel's build
@@ -296,6 +317,7 @@ 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
@@ -372,6 +394,35 @@ CIBW_TEST_COMMAND: pytest {package}/tests
```
### `CIBW_BEFORE_TEST` {: #before-test}
> Execute a shell command before testing each wheel
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. 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`.
Platform-specific variants also available:<br/>
`CIBW_BEFORE_TEST_MACOS` | `CIBW_BEFORE_TEST_WINDOWS` | `CIBW_BEFORE_TEST_LINUX`
#### Examples
```yaml
# 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
CIBW_BEFORE_TEST: cd some_dir; ./configure; make; make install
# install python packages that are required to install test dependencies
CIBW_BEFORE_TEST: pip install cmake scikit-build
```
### `CIBW_TEST_REQUIRES` {: #test-requires}
> Install Python dependencies before running the tests
@@ -411,33 +462,6 @@ Platform-specific variants also available:<br/>
CIBW_TEST_EXTRAS: test,qt
```
### `CIBW_BEFORE_TEST` {: #before-test}
> Execute a shell command before testing each wheel
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. 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`.
Platform-specific variants also available:<br/>
`CIBW_BEFORE_TEST_MACOS` | `CIBW_BEFORE_TEST_WINDOWS` | `CIBW_BEFORE_TEST_LINUX`
#### Examples
```yaml
# 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
CIBW_BEFORE_TEST: cd some_dir; ./configure; make; make install
# install python packages that are required to install test dependencies
CIBW_BEFORE_TEST: pip install cmake scikit-build
```
## Other
@@ -601,7 +625,7 @@ optional arguments:
var url = 'https://cibuildwheel.readthedocs.io/en/stable/options/#'+option.id;
var namesMarkdown = $.map(optionNames, function(n) {
return '[`'+n+'`]('+url+') '
}).join(' ')
}).join(' <br> ')
markdown += '| '+namesMarkdown+' '
markdown += '| '+option.description.trim()+' '
+4
View File
@@ -15,6 +15,8 @@ Commit this file, and push to Github - either to your default branch, or to a PR
For more info on this file, check out the [docs](https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions).
[`examples/github-deploy.yml`](https://github.com/joerick/cibuildwheel/blob/master/examples/github-deploy.yml) extends this minimal example with a demonstration of how to automatically upload the built wheels to PyPI.
# Azure Pipelines [linux/mac/windows] {: #azure-pipelines}
To build Linux, Mac, and Windows wheels on Azure Pipelines, create a `azure-pipelines.yml` file in your repo.
@@ -46,6 +48,8 @@ Commit this file, enable building of your repo on Travis CI, and push.
Then setup a deployment method by following the [Travis CI deployment docs](https://docs.travis-ci.com/user/deployment/), or see [Delivering to PyPI](deliver-to-pypi.md). For more info on `.travis.yml`, check out the [docs](https://docs.travis-ci.com/).
[`examples/travis-deploy.yml`](https://github.com/joerick/cibuildwheel/blob/master/examples/travis-deploy.yml) extends this minimal example with a demonstration of how to automatically upload the built wheels to PyPI.
# CircleCI [linux/mac] {: #circleci}
To build Linux and Mac wheels on CircleCI, create a `.circleci/config.yml` file in your repo,
+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.4.1
install: python -m pip install cibuildwheel==1.5.3
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.4.1
pip3 install cibuildwheel==1.5.3
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.4.1
pip3 install cibuildwheel==1.5.3
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.4.1
pip install cibuildwheel==1.5.3
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.4.1
pip3 install --user cibuildwheel==1.5.3
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.4.1
pip3 install --user cibuildwheel==1.5.3
cibuildwheel --output-dir wheelhouse
- store_artifacts:
path: wheelhouse/
+81
View File
@@ -0,0 +1,81 @@
name: Build and upload to PyPI
# Build on every branch push, tag push, and pull request change:
on: [push, pull_request]
# Alternatively, to publish when a (published) GitHub Release is created, use the following:
# on:
# push:
# pull_request:
# release:
# types:
# - published
jobs:
build_wheels:
name: Build wheels on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-18.04, windows-latest, macos-latest]
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
name: Install Python
with:
python-version: '3.7'
- name: Install cibuildwheel
run: |
python -m pip install cibuildwheel==1.5.3
- name: Install Visual C++ for Python 2.7
if: runner.os == 'Windows'
run: |
choco install vcpython27 -f -y
- name: Build wheels
run: |
python -m cibuildwheel --output-dir wheelhouse
- uses: actions/upload-artifact@v2
with:
path: ./wheelhouse/*.whl
build_sdist:
name: Build source distribution
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
name: Install Python
with:
python-version: '3.7'
- name: Build sdist
run: python setup.py sdist
- uses: actions/upload-artifact@v2
with:
path: dist/*.tar.gz
upload_pypi:
needs: [build_wheels, build_sdist]
runs-on: ubuntu-latest
# upload to PyPI on every tag starting with 'v'
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/v')
# alternatively, to publish when a GitHub Release is created, use the following rule:
# if: github.event_name == 'release' && github.event.action == 'published'
steps:
- uses: actions/download-artifact@v2
with:
name: artifact
path: dist
- uses: pypa/gh-action-pypi-publish@master
with:
user: __token__
password: ${{ secrets.pypi_password }}
# To test: repository_url: https://test.pypi.org/legacy/
+19 -20
View File
@@ -4,34 +4,33 @@ on: [push, pull_request]
jobs:
build_wheels:
name: Build wheel on ${{ matrix.os }}
name: Build wheels on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-18.04, windows-latest, macos-latest]
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
- uses: actions/setup-python@v1
name: Install Python
with:
python-version: '3.7'
- uses: actions/setup-python@v2
name: Install Python
with:
python-version: '3.7'
- name: Install cibuildwheel
run: |
python -m pip install cibuildwheel==1.4.1
- name: Install cibuildwheel
run: |
python -m pip install cibuildwheel==1.5.3
- name: Install Visual C++ for Python 2.7
if: startsWith(matrix.os, 'windows')
run: |
choco install vcpython27 -f -y
- name: Install Visual C++ for Python 2.7
if: runner.os == 'Windows'
run: |
choco install vcpython27 -f -y
- name: Build wheel
run: |
python -m cibuildwheel --output-dir wheelhouse
- name: Build wheels
run: |
python -m cibuildwheel --output-dir wheelhouse
- uses: actions/upload-artifact@v1
with:
name: wheels
path: ./wheelhouse
- uses: actions/upload-artifact@v2
with:
path: ./wheelhouse/*.whl
@@ -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.4.1
- python3 -m pip install cibuildwheel==1.5.3
script:
# build the wheels, put them into './wheelhouse'
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
- ln -s /c/Python38/python.exe /c/Python38/python3.exe
install:
- python3 -m pip install cibuildwheel==1.4.1
- python3 -m pip install cibuildwheel==1.5.3
script:
# build the wheels, put them into './wheelhouse'
+3 -4
View File
@@ -7,7 +7,6 @@
language: python
python:
- 3.5
- 3.6
- 3.7
- 3.8
@@ -56,7 +55,7 @@ jobs:
- stage: deploy
name: Build and deploy Linux wheels
services: docker
install: python3 -m pip install cibuildwheel==1.4.1
install: python3 -m pip install cibuildwheel==1.5.3
script: python3 -m cibuildwheel --output-dir wheelhouse
after_success: |
python3 -m pip install twine
@@ -66,7 +65,7 @@ jobs:
name: Build and deploy macOS wheels
os: osx
language: shell
install: python3 -m pip install cibuildwheel==1.4.1
install: python3 -m pip install cibuildwheel==1.5.3
script: python3 -m cibuildwheel --output-dir wheelhouse
after_success: |
python3 -m pip install twine
@@ -76,7 +75,7 @@ jobs:
name: Build and deploy Windows wheels
os: windows
language: shell
install: python3 -m pip install cibuildwheel==1.4.1
install: python3 -m pip install cibuildwheel==1.5.3
script: python3 -m cibuildwheel --output-dir wheelhouse
after_success: |
python3 -m pip install twine
+2
View File
@@ -6,3 +6,5 @@ pymdown-extensions
pip-tools
requests
click
mypy
typing-extensions
+28 -1
View File
@@ -1,5 +1,5 @@
[flake8]
ignore = E501,W503
ignore = E501,W503,E741,E226
application-import-names = cibuildwheel
exclude =
cibuildwheel/resources/,
@@ -11,3 +11,30 @@ exclude =
env??/,
.venv/,
site/
[tool:pytest]
junit_family=xunit2
[mypy]
python_version = 3.6
files = cibuildwheel,test
warn_unused_configs = True
warn_redundant_casts = True
[mypy-cibuildwheel]
disallow_any_generics = True
disallow_subclassing_any = True
disallow_untyped_calls = True
disallow_untyped_defs = True
disallow_incomplete_defs = True
check_untyped_defs = True
disallow_untyped_decorators = True
no_implicit_optional = True
warn_unused_ignores = True
warn_return_any = True
no_implicit_reexport = True
strict_equality = True
[mypy-pytest,setuptools]
ignore_missing_imports = True
+6 -8
View File
@@ -1,20 +1,18 @@
# -*- coding: utf-8 -*-
import io
import os
from pathlib import Path
try:
from setuptools import setup
except ImportError:
from distutils.core import setup
this_directory = os.path.dirname(__file__)
with io.open(os.path.join(this_directory, 'README.md'), encoding='utf-8') as f:
long_description = f.read()
this_directory = Path(__file__).parent
long_description = (this_directory / 'README.md').read_text(encoding='utf-8')
setup(
name='cibuildwheel',
version='1.4.1',
install_requires=['bashlex!=0.13'],
version='1.5.3',
install_requires=['bashlex!=0.13', 'toml'],
description="Build Python wheels on CI with minimal configuration.",
long_description=long_description,
long_description_content_type='text/markdown',
@@ -28,7 +26,7 @@ setup(
'cibuildwheel': ['resources/*'],
},
# Supported python versions
python_requires='>=3.5',
python_requires='>=3.6',
keywords='ci wheel packaging pypi travis appveyor macos linux windows',
classifiers=[
'Intended Audience :: Developers',
-15
View File
@@ -1,15 +0,0 @@
import os
from setuptools import (
Extension,
setup,
)
if os.environ.get('CIBUILDWHEEL', '0') != '1':
raise Exception('CIBUILDWHEEL environment variable is not set to 1')
setup(
name="spam",
ext_modules=[Extension('spam', sources=['spam.c'])],
version="0.1.0",
)
-48
View File
@@ -1,48 +0,0 @@
#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)
}
-55
View File
@@ -1,55 +0,0 @@
import os
import subprocess
import pytest
import utils
def test():
project_dir = os.path.dirname(__file__)
# build and test the wheels
actual_wheels = utils.cibuildwheel_run(project_dir, add_env={
'CIBW_TEST_REQUIRES': 'nose',
# the 'false ||' bit is to ensure this command runs in a shell on
# mac/linux.
'CIBW_TEST_COMMAND': 'false || nosetests {project}/test',
'CIBW_TEST_COMMAND_WINDOWS': 'COLOR 00 || nosetests {project}/test',
})
# also check that we got the right wheels
expected_wheels = utils.expected_wheels('spam', '0.1.0')
assert set(actual_wheels) == set(expected_wheels)
def test_extras_require():
project_dir = os.path.dirname(__file__)
# build and test the wheels
actual_wheels = utils.cibuildwheel_run(project_dir, add_env={
'CIBW_TEST_EXTRAS': 'test',
# the 'false ||' bit is to ensure this command runs in a shell on
# mac/linux.
'CIBW_TEST_COMMAND': 'false || nosetests {project}/test',
'CIBW_TEST_COMMAND_WINDOWS': 'COLOR 00 || nosetests {project}/test',
})
# also check that we got the right wheels
expected_wheels = utils.expected_wheels('spam', '0.1.0')
assert set(actual_wheels) == set(expected_wheels)
def test_failing_test(tmp_path):
'''Ensure a failing test causes cibuildwheel to error out and exit'''
project_dir = os.path.dirname(__file__)
with pytest.raises(subprocess.CalledProcessError):
utils.cibuildwheel_run(project_dir, output_dir=tmp_path, add_env={
'CIBW_TEST_COMMAND': 'false',
# manylinux1 has a version of bash that's been shown to have
# problems with this, so let's check that.
'CIBW_MANYLINUX_I686_IMAGE': 'manylinux1',
'CIBW_MANYLINUX_X86_64_IMAGE': 'manylinux1',
})
assert len(os.listdir(str(tmp_path))) == 0
-11
View File
@@ -1,11 +0,0 @@
from setuptools import (
Extension,
setup,
)
setup(
name="spam",
ext_modules=[Extension('spam', sources=['spam.c'])],
extras_require={'test': ['nose']},
version="0.1.0",
)
-48
View File
@@ -1,48 +0,0 @@
#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)
}
-56
View File
@@ -1,56 +0,0 @@
from __future__ import print_function
import os
import platform
import sys
import struct
from unittest import TestCase
import spam
def path_contains(parent, child):
''' returns True if `child` is inside `parent`.
Works around path-comparison bugs caused by short-paths on Windows e.g.
vssadm~1 instead of vssadministrator
'''
parent = os.path.abspath(parent)
child = os.path.abspath(child)
while child != os.path.dirname(child):
child = os.path.dirname(child)
if os.stat(parent) == os.stat(child):
# parent and child refer to the same directory on the filesystem
return True
return False
class TestSpam(TestCase):
def test_system(self):
self.assertEqual(0, spam.system('python -c "exit(0)"'))
self.assertNotEqual(0, spam.system('python -c "exit(1)"'))
def test_virtualenv(self):
virtualenv_path = os.environ.get("__CIBW_VIRTUALENV_PATH__")
if not virtualenv_path:
self.fail("No virtualenv path defined in environment variable __CIBW_VIRTUALENV_PATH__")
print("=[executable]", sys.executable)
print("=[spam location]", spam.__file__)
print("=[virtualenv path]", virtualenv_path)
print("=[listdir]", os.listdir(virtualenv_path))
if os.path.exists(os.path.join(virtualenv_path, 'Scripts')):
print("=[listdir]2", os.listdir(os.path.join(virtualenv_path, 'Scripts')))
if os.path.exists(os.path.join(virtualenv_path, 'bin')):
print("=[listdir]2", os.listdir(os.path.join(virtualenv_path, 'bin')))
self.assertTrue(path_contains(virtualenv_path, sys.executable))
self.assertTrue(path_contains(virtualenv_path, spam.__file__))
def test_uname(self):
if platform.system() == "Windows":
return
# if we're running in 32-bit Python, check that the machine is i686.
# See #336 for more info.
bits = struct.calcsize("P") * 8
if bits == 32:
self.assertEqual(platform.machine(), "i686")
-19
View File
@@ -1,19 +0,0 @@
import os
import utils
def test():
project_dir = os.path.dirname(__file__)
# build the wheels
actual_wheels = utils.cibuildwheel_run(project_dir, add_env={
# write python version information to a temporary file, this is
# checked in setup.py
'CIBW_BEFORE_BUILD': '''python -c "import sys; open('/tmp/pythonversion.txt', 'w').write(sys.version)" && python -c "import sys; open('/tmp/pythonexecutable.txt', 'w').write(sys.executable)"''',
'CIBW_BEFORE_BUILD_WINDOWS': '''python -c "import sys; open('c:\\pythonversion.txt', 'w').write(sys.version)" && python -c "import sys; open('c:\\pythonexecutable.txt', 'w').write(sys.executable)"''',
})
# also check that we got the right wheels
expected_wheels = utils.expected_wheels('spam', '0.1.0')
assert set(actual_wheels) == set(expected_wheels)
-31
View File
@@ -1,31 +0,0 @@
import os
import sys
from setuptools import (
Extension,
setup,
)
# assert that the Python version as written to pythonversion.txt in the CIBW_BEFORE_BUILD step
# is the same one as is currently running.
version_file = 'c:\\pythonversion.txt' if sys.platform == 'win32' else '/tmp/pythonversion.txt'
with open(version_file) as f:
stored_version = f.read()
print('stored_version', stored_version)
print('sys.version', sys.version)
assert stored_version == sys.version
# check that the executable also was written
executable_file = 'c:\\pythonexecutable.txt' if sys.platform == 'win32' else '/tmp/pythonexecutable.txt'
with open(executable_file) as f:
stored_executable = f.read()
print('stored_executable', stored_executable)
print('sys.executable', sys.executable)
# windows/mac are case insensitive
assert os.path.realpath(stored_executable).lower() == os.path.realpath(sys.executable).lower()
setup(
name="spam",
ext_modules=[Extension('spam', sources=['spam.c'])],
version="0.1.0",
)
-48
View File
@@ -1,48 +0,0 @@
#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)
}
-18
View File
@@ -1,18 +0,0 @@
import os
import utils
def test():
project_dir = os.path.dirname(__file__)
# build the wheels
actual_wheels = utils.cibuildwheel_run(project_dir, add_env={
'CIBW_BUILD': 'cp3?-*',
'CIBW_SKIP': 'cp37-*',
})
# check that we got the right wheels. There should be no 2.7 or 3.7.
expected_wheels = [w for w in utils.expected_wheels('spam', '0.1.0')
if ('-cp3' in w) and ('-cp37' not in w)]
assert set(actual_wheels) == set(expected_wheels)
-18
View File
@@ -1,18 +0,0 @@
import sys
from setuptools import (
Extension,
setup,
)
# explode if run on Python 2.7 or Python 3.4 (these should be skipped)
if sys.version_info[0:2] == (2, 7):
raise Exception('Python 2.7 should not be built')
if sys.version_info[0:2] == (3, 4):
raise Exception('Python 3.4 should be skipped')
setup(
name="spam",
ext_modules=[Extension('spam', sources=['spam.c'])],
version="0.1.0",
)
-48
View File
@@ -1,48 +0,0 @@
#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)
}
-32
View File
@@ -1,32 +0,0 @@
import os
import pytest
import subprocess
import utils
def test():
project_dir = os.path.dirname(__file__)
# write some information into the CIBW_ENVIRONMENT, for expansion and
# insertion into the environment by cibuildwheel. This is checked
# in setup.py
actual_wheels = utils.cibuildwheel_run(project_dir, add_env={
'CIBW_ENVIRONMENT': '''CIBW_TEST_VAR="a b c" CIBW_TEST_VAR_2=1 CIBW_TEST_VAR_3="$(echo 'test string 3')" PATH=$PATH:/opt/cibw_test_path''',
'CIBW_ENVIRONMENT_WINDOWS': '''CIBW_TEST_VAR="a b c" CIBW_TEST_VAR_2=1 CIBW_TEST_VAR_3="$(echo 'test string 3')" PATH="$PATH;/opt/cibw_test_path"''',
})
# also check that we got the right wheels built
expected_wheels = utils.expected_wheels('spam', '0.1.0')
assert set(actual_wheels) == set(expected_wheels)
def test_overridden_path(tmp_path):
project_dir = os.path.dirname(__file__)
# mess up PATH, somehow
with pytest.raises(subprocess.CalledProcessError):
utils.cibuildwheel_run(project_dir, output_dir=tmp_path, add_env={
'CIBW_ENVIRONMENT': '''SOMETHING="$(mkdir new_path && touch new_path/python)" PATH="$(realpath new_path):$PATH"''',
'CIBW_ENVIRONMENT_WINDOWS': '''SOMETHING="$(mkdir new_path && type nul > new_path/python.exe)" PATH="$CD\\new_path;$PATH"''',
})
assert len(os.listdir(str(tmp_path))) == 0
-26
View File
@@ -1,26 +0,0 @@
import os
from setuptools import (
Extension,
setup,
)
# explode if environment isn't correct, as set in CIBW_ENVIRONMENT
CIBW_TEST_VAR = os.environ.get('CIBW_TEST_VAR')
CIBW_TEST_VAR_2 = os.environ.get('CIBW_TEST_VAR_2')
PATH = os.environ.get('PATH')
if CIBW_TEST_VAR != 'a b c':
raise Exception('CIBW_TEST_VAR should equal "a b c". It was "%s"' % CIBW_TEST_VAR)
if CIBW_TEST_VAR_2 != '1':
raise Exception('CIBW_TEST_VAR_2 should equal "1". It was "%s"' % CIBW_TEST_VAR_2)
if '/opt/cibw_test_path' not in PATH:
raise Exception('PATH should contain "/opt/cibw_test_path". It was "%s"' % PATH)
if '$PATH' in PATH:
raise Exception('$PATH should be expanded in PATH. It was "%s"' % PATH)
setup(
name="spam",
ext_modules=[Extension('spam', sources=['spam.c'])],
version="0.1.0",
)
-48
View File
@@ -1,48 +0,0 @@
#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)
}
-18
View File
@@ -1,18 +0,0 @@
import os
import sys
from setuptools import (
Extension,
setup,
)
# check that we're running in the correct docker image as specified in the
# environment options CIBW_MANYLINUX1_*_IMAGE
if 'linux' in sys.platform and not os.path.exists('/dockcross'):
raise Exception('/dockcross directory not found. Is this test running in the correct docker image?')
setup(
name="spam",
ext_modules=[Extension('spam', sources=['spam.c'])],
version="0.1.0",
)
-48
View File
@@ -1,48 +0,0 @@
#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)
}
-11
View File
@@ -1,11 +0,0 @@
import os
import utils
def test():
project_dir = os.path.dirname(__file__)
# this test checks that SSL is working in the build environment using
# some checks in setup.py.
utils.cibuildwheel_run(project_dir)
-24
View File
@@ -1,24 +0,0 @@
import ssl
import sys
from setuptools import (
Extension,
setup,
)
if sys.version_info[0] == 2:
from urllib2 import urlopen
else:
from urllib.request import urlopen
if sys.version_info[0:2] == (3, 3):
data = urlopen('https://www.nist.gov')
else:
context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
data = urlopen('https://www.nist.gov', context=context)
setup(
name="spam",
ext_modules=[Extension('spam', sources=['spam.c'])],
version="0.1.0",
)
-48
View File
@@ -1,48 +0,0 @@
#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)
}
-10
View File
@@ -1,10 +0,0 @@
from setuptools import (
Extension,
setup,
)
setup(
name="spam",
ext_modules=[Extension('spam', sources=['spam.c'])],
version="0.1.0",
)
-68
View File
@@ -1,68 +0,0 @@
#include <Python.h>
#include <malloc.h>
#if !defined(__GLIBC_PREREQ)
#error "Must run on a glibc linux environment"
#endif
#if !__GLIBC_PREREQ(2, 5) /* manylinux1 is glibc 2.5 */
#error "Must run on a glibc >= 2.5 linux environment"
#endif
static PyObject *
spam_system(PyObject *self, PyObject *args)
{
const char *command;
int sts = 0;
if (!PyArg_ParseTuple(args, "s", &command))
return NULL;
#if defined(__GLIBC_PREREQ) && __GLIBC_PREREQ(2, 17) /* manylinux2014 is glibc 2.17 */
// secure_getenv is only available in manylinux2014, ensuring
// that only a manylinux2014 wheel is produced
sts = (int)secure_getenv("NON_EXISTING_ENV_VARIABLE");
#elif defined(__GLIBC_PREREQ) && __GLIBC_PREREQ(2, 10) /* manylinux2010 is glibc 2.12 */
// malloc_info is only available on manylinux2010+
sts = malloc_info(0, stdout);
#endif
if (sts == 0) {
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)
}
-14
View File
@@ -1,14 +0,0 @@
import os
import utils
project_dir = os.path.dirname(__file__)
def test():
# build the wheels
actual_wheels = utils.cibuildwheel_run(project_dir)
# check that the expected wheels are produced
expected_wheels = utils.expected_wheels('spam', '0.1.0')
assert set(actual_wheels) == set(expected_wheels)
-6
View File
@@ -1,6 +0,0 @@
[metadata]
name = spam
version = attr: spam.__version__
[options]
packages = find:
-8
View File
@@ -1,8 +0,0 @@
from setuptools import (
Extension,
setup,
)
setup(
ext_modules=[Extension('spam.spam', sources=['spam/spam.c'])],
)
-1
View File
@@ -1 +0,0 @@
__version__ = "0.1.0"
-48
View File
@@ -1,48 +0,0 @@
#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,61 +0,0 @@
import os
import pytest
import utils
project_dir = os.path.dirname(__file__)
def test_cpp11(tmp_path):
# This test checks that the C++11 standard is supported
# VC++ for Python 2.7 does not support modern standards
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')
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
# 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
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')
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)
def test_cpp17():
# This test checks that the C++17 standard is supported
# Python and PyPy 2.7 use the `register` keyword which is forbidden in the C++17 standard
# The manylinux1 docker image does not have a compiler which supports C++11
# Python 3.5 and PyPy 3.6 are compiled with MSVC 10, which does not support C++17
if os.environ.get('APPVEYOR_BUILD_WORKER_IMAGE', '') == 'Visual Studio 2015':
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]
assert set(actual_wheels) == set(expected_wheels)
-25
View File
@@ -1,25 +0,0 @@
import os
import platform
from setuptools import (
Extension,
setup,
)
standard = os.environ["STANDARD"]
language_standard = "/std:c++" + standard if platform.system() == "Windows" else "-std=c++" + standard
extra_compile_args = [language_standard, "-DSTANDARD=" + standard]
if standard == "17":
if platform.system() == "Windows":
extra_compile_args.append("/wd5033")
else:
extra_compile_args.append("-Wno-register")
setup(
name="spam",
ext_modules=[Extension('spam', sources=['spam.cpp'], language="c++", extra_compile_args=extra_compile_args)],
version="0.1.0",
)
-62
View File
@@ -1,62 +0,0 @@
#include <Python.h>
#include <string>
// Depending on the requested standard, use a modern C++ feature
// that was introduced in that standard.
#if STANDARD == 11
#include <array>
#elif STANDARD == 14
int a = 100'000;
#elif STANDARD == 17
#include <utility>
auto a = std::pair(5.0, false);
#else
#error Standard needed
#endif
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)
}
-23
View File
@@ -1,23 +0,0 @@
import os
import utils
def test():
project_dir = os.path.dirname(__file__)
# build the wheels
actual_wheels = utils.cibuildwheel_run(project_dir, add_env={
# write python version information to a temporary file, this is
# checked in setup.py
'CIBW_BEFORE_TEST': '''python -c "import sys; open('/tmp/pythonversion.txt', 'w').write(sys.version)" && python -c "import sys; open('/tmp/pythonprefix.txt', 'w').write(sys.prefix)"''',
'CIBW_BEFORE_TEST_WINDOWS': '''python -c "import sys; open('c:\\pythonversion.txt', 'w').write(sys.version)" && python -c "import sys; open('c:\\pythonprefix.txt', 'w').write(sys.prefix)"''',
'CIBW_TEST_REQUIRES': 'nose',
# the 'false ||' bit is to ensure this command runs in a shell on
# mac/linux.
'CIBW_TEST_COMMAND': 'false || nosetests {project}/test',
'CIBW_TEST_COMMAND_WINDOWS': 'nosetests {project}/test',
})
# also check that we got the right wheels
expected_wheels = utils.expected_wheels('spam', '0.1.0')
assert set(actual_wheels) == set(expected_wheels)
-8
View File
@@ -1,8 +0,0 @@
from setuptools import setup, Extension
setup(
name="spam",
ext_modules=[Extension('spam', sources=['spam.c'])],
version="0.1.0",
)
-48
View File
@@ -1,48 +0,0 @@
#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)
}
-28
View File
@@ -1,28 +0,0 @@
import sys
import os
from unittest import TestCase
class TestBeforeTest(TestCase):
def test_version(self):
# assert that the Python version as written to pythonversion.txt in the CIBW_BEFORE_TEST step
# is the same one as is currently running.
# because of use symlinks in MacOS run this test is also need
version_file = 'c:\\pythonversion.txt' if sys.platform == 'win32' else '/tmp/pythonversion.txt'
with open(version_file) as f:
stored_version = f.read()
print('stored_version', stored_version)
print('sys.version', sys.version)
assert stored_version == sys.version
def test_prefix(self):
# check that the prefix also was written
prefix_file = 'c:\\pythonprefix.txt' if sys.platform == 'win32' else '/tmp/pythonprefix.txt'
with open(prefix_file) as f:
stored_prefix = f.read()
print('stored_prefix', stored_prefix)
print('sys.prefix', sys.prefix)
# Works around path-comparison bugs caused by short-paths on Windows e.g.
# vssadm~1 instead of vssadministrator
assert os.stat(stored_prefix) == os.stat(sys.prefix)
-31
View File
@@ -1,31 +0,0 @@
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
@@ -1,48 +0,0 @@
#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 +0,0 @@
print('before_build.py executed!')
@@ -1,25 +0,0 @@
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
@@ -1,8 +0,0 @@
from setuptools import Extension, setup
setup(
name="spam",
ext_modules=[Extension('spam', sources=['spam.c'])],
version="0.1.0",
)
-48
View File
@@ -1,48 +0,0 @@
#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 +0,0 @@
print('run_tests.py executed!')
+1 -3
View File
@@ -1,3 +1 @@
This folder contains repos that are built by cibuildwheel to test cibuildwheel. Confusing, I know.
Basically, if cibuildwheel can build these projects, we're good.
This folder contains integration tests for cibuildwheel.
View File
-4
View File
@@ -1,4 +0,0 @@
import os
import sys
sys.path.append(os.path.join(os.path.dirname(__file__), 'shared'))
@@ -1,12 +1,22 @@
import os
import textwrap
import platform
from . import test_projects
from . import utils
import utils
basic_project = test_projects.new_c_project(
setup_py_add=textwrap.dedent('''
import os
project_dir = os.path.dirname(__file__)
if os.environ.get("CIBUILDWHEEL", "0") != "1":
raise Exception("CIBUILDWHEEL environment variable is not set to 1")
''')
)
def test():
def test(tmp_path):
project_dir = tmp_path / 'project'
basic_project.generate(project_dir)
# build the wheels
actual_wheels = utils.cibuildwheel_run(project_dir)
@@ -15,7 +25,10 @@ def test():
assert set(actual_wheels) == set(expected_wheels)
def test_build_identifiers():
def test_build_identifiers(tmp_path):
project_dir = tmp_path / 'project'
basic_project.generate(project_dir)
# check that the number of expected wheels matches the number of build
# identifiers
# after adding CIBW_MANYLINUX_IMAGE to support manylinux2010, there
+38
View File
@@ -0,0 +1,38 @@
import os
import textwrap
from . import utils
from . import test_projects
project_with_before_build_asserts = test_projects.new_c_project(
setup_py_add=textwrap.dedent(r'''
# assert that the Python version as written to text_info.txt in the CIBW_BEFORE_ALL step
# is the same one as is currently running.
with open("text_info.txt") as f:
stored_text = f.read()
print("## stored text: " + stored_text)
assert stored_text == "sample text 123"
''')
)
def test(tmp_path):
project_dir = tmp_path / 'project'
project_with_before_build_asserts.generate(project_dir)
with open(os.path.join(project_dir, "text_info.txt"), mode='w') as ff:
print("dummy text", file=ff)
# build the wheels
actual_wheels = utils.cibuildwheel_run(project_dir, add_env={
# write python version information to a temporary file, this is
# checked in setup.py
'CIBW_BEFORE_ALL': '''python -c "import os;open('{project}/text_info.txt', 'w').write('sample text '+os.environ.get('TEST_VAL', ''))"''',
'CIBW_ENVIRONMENT': "TEST_VAL='123'"
})
# also check that we got the right wheels
os.remove(os.path.join(project_dir, "text_info.txt"))
expected_wheels = utils.expected_wheels('spam', '0.1.0')
assert set(actual_wheels) == set(expected_wheels)
+45
View File
@@ -0,0 +1,45 @@
import textwrap
from . import utils
from . import test_projects
project_with_before_build_asserts = test_projects.new_c_project(
setup_py_add=textwrap.dedent(r'''
import sys, os
# assert that the Python version as written to pythonversion.txt in the CIBW_BEFORE_BUILD step
# is the same one as is currently running.
version_file = 'c:\\pythonversion.txt' if sys.platform == 'win32' else '/tmp/pythonversion.txt'
with open(version_file) as f:
stored_version = f.read()
print('stored_version', stored_version)
print('sys.version', sys.version)
assert stored_version == sys.version
# check that the executable also was written
executable_file = 'c:\\pythonexecutable.txt' if sys.platform == 'win32' else '/tmp/pythonexecutable.txt'
with open(executable_file) as f:
stored_executable = f.read()
print('stored_executable', stored_executable)
print('sys.executable', sys.executable)
# windows/mac are case insensitive
assert os.path.realpath(stored_executable).lower() == os.path.realpath(sys.executable).lower()
''')
)
def test(tmp_path):
project_dir = tmp_path / 'project'
project_with_before_build_asserts.generate(project_dir)
# build the wheels
actual_wheels = utils.cibuildwheel_run(project_dir, add_env={
# write python version information to a temporary file, this is
# checked in setup.py
'CIBW_BEFORE_BUILD': '''python -c "import sys; open('/tmp/pythonversion.txt', 'w').write(sys.version)" && python -c "import sys; open('/tmp/pythonexecutable.txt', 'w').write(sys.executable)"''',
'CIBW_BEFORE_BUILD_WINDOWS': '''python -c "import sys; open('c:\\pythonversion.txt', 'w').write(sys.version)" && python -c "import sys; open('c:\\pythonexecutable.txt', 'w').write(sys.executable)"''',
})
# also check that we got the right wheels
expected_wheels = utils.expected_wheels('spam', '0.1.0')
assert set(actual_wheels) == set(expected_wheels)
+56
View File
@@ -0,0 +1,56 @@
from . import test_projects
from . import utils
before_test_project = test_projects.new_c_project()
before_test_project.files['test/spam_test.py'] = r'''
import sys
import os
from unittest import TestCase
class TestBeforeTest(TestCase):
def test_version(self):
# assert that the Python version as written to pythonversion.txt in the CIBW_BEFORE_TEST step
# is the same one as is currently running.
# because of use symlinks in MacOS run this test is also need
version_file = 'c:\\pythonversion.txt' if sys.platform == 'win32' else '/tmp/pythonversion.txt'
with open(version_file) as f:
stored_version = f.read()
print('stored_version', stored_version)
print('sys.version', sys.version)
assert stored_version == sys.version
def test_prefix(self):
# check that the prefix also was written
prefix_file = 'c:\\pythonprefix.txt' if sys.platform == 'win32' else '/tmp/pythonprefix.txt'
with open(prefix_file) as f:
stored_prefix = f.read()
print('stored_prefix', stored_prefix)
print('sys.prefix', sys.prefix)
# Works around path-comparison bugs caused by short-paths on Windows e.g.
# vssadm~1 instead of vssadministrator
assert os.stat(stored_prefix) == os.stat(sys.prefix)
'''
def test(tmp_path):
project_dir = tmp_path / 'project'
before_test_project.generate(project_dir)
# build the wheels
actual_wheels = utils.cibuildwheel_run(project_dir, add_env={
# write python version information to a temporary file, this is
# checked in setup.py
'CIBW_BEFORE_TEST': '''python -c "import sys; open('/tmp/pythonversion.txt', 'w').write(sys.version)" && python -c "import sys; open('/tmp/pythonprefix.txt', 'w').write(sys.prefix)"''',
'CIBW_BEFORE_TEST_WINDOWS': '''python -c "import sys; open('c:\\pythonversion.txt', 'w').write(sys.version)" && python -c "import sys; open('c:\\pythonprefix.txt', 'w').write(sys.prefix)"''',
'CIBW_TEST_REQUIRES': 'nose',
# the 'false ||' bit is to ensure this command runs in a shell on
# mac/linux.
'CIBW_TEST_COMMAND': 'false || nosetests {project}/test',
'CIBW_TEST_COMMAND_WINDOWS': 'nosetests {project}/test',
})
# also check that we got the right wheels
expected_wheels = utils.expected_wheels('spam', '0.1.0')
assert set(actual_wheels) == set(expected_wheels)
+30
View File
@@ -0,0 +1,30 @@
import textwrap
from . import utils
from . import test_projects
project_with_skip_asserts = test_projects.new_c_project(
setup_py_add=textwrap.dedent(r'''
# explode if run on Python 2.7 or Python 3.7 (these should be skipped)
if sys.version_info[0:2] == (2, 7):
raise Exception("Python 2.7 should not be built")
if sys.version_info[0:2] == (3, 7):
raise Exception("Python 3.7 should be skipped")
''')
)
def test(tmp_path):
project_dir = tmp_path / 'project'
project_with_skip_asserts.generate(project_dir)
# build the wheels
actual_wheels = utils.cibuildwheel_run(project_dir, add_env={
'CIBW_BUILD': 'cp3?-*',
'CIBW_SKIP': 'cp37-*',
})
# check that we got the right wheels. There should be no 2.7 or 3.7.
expected_wheels = [w for w in utils.expected_wheels('spam', '0.1.0')
if ('-cp3' in w) and ('-cp37' not in w)]
assert set(actual_wheels) == set(expected_wheels)
+209
View File
@@ -0,0 +1,209 @@
import os
import jinja2
import pytest
from . import utils
from .test_projects import TestProject
cpp_test_project = TestProject()
cpp_test_project.files['setup.py'] = jinja2.Template(r'''
from setuptools import Extension, setup
setup(
name="spam",
ext_modules=[Extension('spam', sources=['spam.cpp'], language="c++", extra_compile_args={{ extra_compile_args }})],
version="0.1.0",
)
''')
cpp_test_project.files['spam.cpp'] = jinja2.Template(r'''
#include <Python.h>
{{ spam_cpp_top_level_add }}
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)
}
''')
cpp11_project = cpp_test_project.copy()
cpp11_project.template_context['extra_compile_args'] = (
['/std:c++11'] if utils.platform == 'windows' else ['-std=c++11']
)
cpp11_project.template_context['spam_cpp_top_level_add'] = '#include <array>'
def test_cpp11(tmp_path):
# This test checks that the C++11 standard is supported
project_dir = tmp_path / 'project'
cpp11_project.generate(project_dir)
# VC++ for Python 2.7 does not support modern standards
add_env = {'CIBW_SKIP': 'cp27-win* pp27-win32'}
actual_wheels = utils.cibuildwheel_run(project_dir, add_env=add_env)
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)
cpp14_project = cpp_test_project.copy()
cpp14_project.template_context['extra_compile_args'] = (
['/std:c++14'] if utils.platform == 'windows' else ['-std=c++14']
)
cpp14_project.template_context['spam_cpp_top_level_add'] = "int a = 100'000;"
def test_cpp14(tmp_path):
# This test checks that the C++14 standard is supported
project_dir = tmp_path / 'project'
cpp14_project.generate(project_dir)
# VC++ for Python 2.7 does not support modern standards
# The manylinux1 docker image does not have a compiler which supports C++11
add_env = {'CIBW_SKIP': 'cp27-win* pp27-win32'}
actual_wheels = utils.cibuildwheel_run(project_dir, add_env=add_env)
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)
cpp17_project = cpp_test_project.copy()
# Python and PyPy 2.7 headers use the `register` keyword, which is forbidden in
# the C++17 standard, so we need the -Wno-register or /wd5033 options
cpp17_project.template_context['extra_compile_args'] = (
['/std:c++17', '/wd5033'] if utils.platform == 'windows' else ['-std=c++17', '-Wno-register']
)
cpp17_project.template_context['spam_cpp_top_level_add'] = r'''
#include <utility>
auto a = std::pair(5.0, false);
'''
def test_cpp17(tmp_path):
# This test checks that the C++17 standard is supported
project_dir = tmp_path / 'project'
cpp17_project.generate(project_dir)
if os.environ.get('APPVEYOR_BUILD_WORKER_IMAGE', '') == 'Visual Studio 2015':
pytest.skip('Visual Studio 2015 does not support C++17')
# Pypy's distutils sets the default compiler to 'msvc9compiler', which
# is too old to support cpp17.
add_env = {'CIBW_SKIP': 'cp27-win* pp??-*'}
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 '-pp' not in w]
assert set(actual_wheels) == set(expected_wheels)
def test_cpp17_py27_modern_msvc_workaround(tmp_path):
# This test checks the workaround for building Python 2.7 wheel with MSVC 14
if utils.platform != 'windows':
pytest.skip('the test is only relevant to the Windows build')
if os.environ.get('APPVEYOR_BUILD_WORKER_IMAGE', '') == 'Visual Studio 2015':
pytest.skip('Visual Studio 2015 does not support C++17')
project_dir = tmp_path / 'project'
cpp17_project.generate(project_dir)
# VC++ for Python 2.7 (i.e., MSVC 9) does not support modern standards
# This is a workaround which forces distutils/setupstools to a newer version
# Wheels compiled need a more modern C++ redistributable installed, which is not
# included with Python: see documentation for more info
# DISTUTILS_USE_SDK and MSSdk=1 tell distutils/setuptools that we are adding
# MSVC's compiler, tools, and libraries to PATH ourselves
add_env = {'DISTUTILS_USE_SDK': '1', 'MSSdk': '1'}
# Use existing setuptools code to run Visual Studio's vcvarsall.bat and get the
# necessary environment variables, since running vcvarsall.bat in a subprocess
# does not keep the relevant environment variables
# There are different environment variables for 32-bit/64-bit targets, so we
# need to run cibuildwheel twice, once for 32-bit with `vcvarsall.bat x86, and
# once for 64-bit with `vcvarsall.bat x64`
# In a normal CI setup, just run vcvarsall.bat before running cibuildwheel and set
# DISTUTILS_USE_SDK and MSSdk
import setuptools
def add_vcvars(prev_env, platform):
vcvarsall_env = setuptools.msvc.msvc14_get_vc_env(platform)
env = prev_env.copy()
for vcvar in ['path', 'include', 'lib']:
env[vcvar] = vcvarsall_env[vcvar]
return env
add_env_x86 = add_vcvars(add_env, 'x86')
add_env_x86['CIBW_BUILD'] = '?p27-win32'
actual_wheels = utils.cibuildwheel_run(project_dir, add_env=add_env_x86)
add_env_x64 = add_vcvars(add_env, 'x64')
add_env_x64['CIBW_BUILD'] = 'cp27-win_amd64'
actual_wheels += utils.cibuildwheel_run(project_dir, add_env=add_env_x64)
expected_wheels = [w for w in utils.expected_wheels('spam', '0.1.0')
if 'cp27-cp27m-win' in w
or 'pp27-pypy_73-win32' in w]
assert set(actual_wheels) == set(expected_wheels)
@@ -1,32 +1,59 @@
import os
import re
import pytest
import textwrap
import cibuildwheel.util
import utils
from . import utils
from . import test_projects
project_with_expected_version_checks = test_projects.new_c_project(
setup_py_add=textwrap.dedent(r'''
import subprocess
import os
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)
)
''')
)
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()
constraint_file_text = constraint_file.read_text(encoding='utf8')
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):
@pytest.mark.parametrize('python_version', ['2.7', '3.5', '3.6', '3.8'])
def test_pinned_versions(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__)
project_dir = tmp_path / 'project'
project_with_expected_version_checks.generate(project_dir)
build_environment = {}
@@ -36,19 +63,22 @@ def test_pinned_versions(python_version):
elif python_version == '3.5':
constraint_filename = 'constraints-python35.txt'
build_pattern = '[cp]p35-*'
elif python_version == '3.6':
constraint_filename = 'constraints-python36.txt'
build_pattern = '[cp]p36-*'
else:
constraint_filename = 'constraints.txt'
build_pattern = '[cp]p38-*'
constraint_file = os.path.join(cibuildwheel.util.resources_dir, constraint_filename)
constraint_file = 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())
env_name = f'EXPECTED_{package.upper()}_VERSION'
build_environment[env_name] = constraint_versions[package]
cibw_environment_option = ' '.join(
['{}={}'.format(k, v) for k, v in build_environment.items()]
[f'{k}={v}' for k, v in build_environment.items()]
)
# build and test the wheels
@@ -64,6 +94,9 @@ def test_pinned_versions(python_version):
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.6':
expected_wheels = [w for w in utils.expected_wheels('spam', '0.1.0')
if '-cp36' in w or '-pp36' 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]
@@ -78,7 +111,8 @@ 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__)
project_dir = tmp_path / 'project'
project_with_expected_version_checks.generate(project_dir)
tool_versions = {
'pip': '20.0.2',
@@ -100,11 +134,11 @@ def test_dependency_constraints_file(tmp_path, python_version):
build_environment = {}
for package_name, version in tool_versions.items():
env_name = 'EXPECTED_{}_VERSION'.format(package_name.upper())
env_name = f'EXPECTED_{package_name.upper()}_VERSION'
build_environment[env_name] = version
cibw_environment_option = ' '.join(
['{}={}'.format(k, v) for k, v in build_environment.items()]
[f'{k}={v}' for k, v in build_environment.items()]
)
# build and test the wheels
@@ -1,19 +1,34 @@
import os
import platform
import textwrap
import pytest
import utils
from . import utils
from . import test_projects
dockcross_only_project = test_projects.new_c_project(
setup_py_add=textwrap.dedent(r'''
import os, sys
# check that we're running in the correct docker image as specified in the
# environment options CIBW_MANYLINUX1_*_IMAGE
if "linux" in sys.platform and not os.path.exists("/dockcross"):
raise Exception(
"/dockcross directory not found. Is this test running in the correct docker image?"
)
''')
)
def test():
project_dir = os.path.dirname(__file__)
def test(tmp_path):
if utils.platform != 'linux':
pytest.skip('the test is only relevant to the linux build')
if platform.machine() not in ['x86_64', 'i686']:
pytest.skip('this test is currently only possible on x86_64/i686 due to availability of alternative images')
project_dir = tmp_path / 'project'
dockcross_only_project.generate(project_dir)
actual_wheels = utils.cibuildwheel_run(project_dir, add_env={
'CIBW_MANYLINUX_X86_64_IMAGE': 'dockcross/manylinux2010-x64',
'CIBW_MANYLINUX_I686_IMAGE': 'dockcross/manylinux2010-x86',
+74
View File
@@ -0,0 +1,74 @@
import os
import pytest
import subprocess
import textwrap
from . import utils
from . import test_projects
project_with_environment_asserts = test_projects.new_c_project(
setup_py_add=textwrap.dedent(r'''
import os
# explode if environment isn't correct, as set in CIBW_ENVIRONMENT
CIBW_TEST_VAR = os.environ.get("CIBW_TEST_VAR")
CIBW_TEST_VAR_2 = os.environ.get("CIBW_TEST_VAR_2")
PATH = os.environ.get("PATH")
if CIBW_TEST_VAR != "a b c":
raise Exception('CIBW_TEST_VAR should equal "a b c". It was "%s"' % CIBW_TEST_VAR)
if CIBW_TEST_VAR_2 != "1":
raise Exception('CIBW_TEST_VAR_2 should equal "1". It was "%s"' % CIBW_TEST_VAR_2)
if "/opt/cibw_test_path" not in PATH:
raise Exception('PATH should contain "/opt/cibw_test_path". It was "%s"' % PATH)
if "$PATH" in PATH:
raise Exception('$PATH should be expanded in PATH. It was "%s"' % PATH)
''')
)
def test(tmp_path):
project_dir = tmp_path / 'project'
project_with_environment_asserts.generate(project_dir)
# write some information into the CIBW_ENVIRONMENT, for expansion and
# insertion into the environment by cibuildwheel. This is checked
# in setup.py
actual_wheels = utils.cibuildwheel_run(project_dir, add_env={
'CIBW_ENVIRONMENT': '''CIBW_TEST_VAR="a b c" CIBW_TEST_VAR_2=1 CIBW_TEST_VAR_3="$(echo 'test string 3')" PATH=$PATH:/opt/cibw_test_path''',
'CIBW_ENVIRONMENT_WINDOWS': '''CIBW_TEST_VAR="a b c" CIBW_TEST_VAR_2=1 CIBW_TEST_VAR_3="$(echo 'test string 3')" PATH="$PATH;/opt/cibw_test_path"''',
})
# also check that we got the right wheels built
expected_wheels = utils.expected_wheels('spam', '0.1.0')
assert set(actual_wheels) == set(expected_wheels)
def test_overridden_path(tmp_path, capfd):
project_dir = tmp_path / 'project'
output_dir = tmp_path / 'output'
project = test_projects.new_c_project()
project.generate(project_dir)
output_dir.mkdir()
# mess up PATH, somehow
with pytest.raises(subprocess.CalledProcessError):
if utils.platform == 'linux':
utils.cibuildwheel_run(project_dir, output_dir=output_dir, add_env={
'CIBW_BEFORE_ALL': 'mkdir new_path && touch new_path/python && chmod +x new_path/python',
'CIBW_ENVIRONMENT': '''PATH="$(pwd)/new_path:$PATH"''',
})
else:
new_path = tmp_path / 'another_bin'
new_path.mkdir()
(new_path / 'python').touch(mode=0o777)
utils.cibuildwheel_run(project_dir, output_dir=output_dir, add_env={
'NEW_PATH': str(new_path),
'CIBW_ENVIRONMENT': f'''PATH="$NEW_PATH{os.pathsep}$PATH"''',
})
assert len(os.listdir(output_dir)) == 0
captured = capfd.readouterr()
assert "python available on PATH doesn't match our installed instance" in captured.err
@@ -1,21 +1,48 @@
import os
import platform
import textwrap
import pytest
import utils
from . import utils
from . import test_projects
# TODO: specify these at runtime according to manylinux_image
project_with_manylinux_symbols = test_projects.new_c_project(
spam_c_top_level_add=textwrap.dedent(r'''
#include <malloc.h>
#if !defined(__GLIBC_PREREQ)
#error "Must run on a glibc linux environment"
#endif
#if !__GLIBC_PREREQ(2, 5) /* manylinux1 is glibc 2.5 */
#error "Must run on a glibc >= 2.5 linux environment"
#endif
'''),
spam_c_function_add=textwrap.dedent(r'''
#if defined(__GLIBC_PREREQ) && __GLIBC_PREREQ(2, 17) /* manylinux2014 is glibc 2.17 */
// secure_getenv is only available in manylinux2014, ensuring
// that only a manylinux2014 wheel is produced
secure_getenv("NON_EXISTING_ENV_VARIABLE");
#elif defined(__GLIBC_PREREQ) && __GLIBC_PREREQ(2, 10) /* manylinux2010 is glibc 2.12 */
// malloc_info is only available on manylinux2010+
malloc_info(0, stdout);
#endif
'''),
)
@pytest.mark.parametrize('manylinux_image', ['manylinux1', 'manylinux2010', 'manylinux2014'])
def test(manylinux_image):
project_dir = os.path.dirname(__file__)
def test(manylinux_image, tmp_path):
if utils.platform != 'linux':
pytest.skip('the docker test is only relevant to the linux build')
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")
project_dir = tmp_path / 'project'
project_with_manylinux_symbols.generate(project_dir)
# build the wheels
# CFLAGS environment variable is necessary to fail on 'malloc_info' (on manylinux1) during compilation/linking,
# rather than when dynamically loading the Python

Some files were not shown because too many files have changed in this diff Show More