Compare commits

...
174 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
Yannick Jadoul edd461ad42 Making order of options more consistent in docs and BuildOptions fields 2020-05-17 22:30:18 +02:00
50 changed files with 1353 additions and 598 deletions
+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
+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
+2
View File
@@ -35,6 +35,8 @@ jobs:
language: shell
before_install:
- 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:\\Python36\\python
+62 -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.2
- 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
----------------
@@ -147,6 +152,46 @@ 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_
@@ -169,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
@@ -199,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
@@ -209,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
@@ -232,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)
@@ -245,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
@@ -282,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)
@@ -312,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
-1
View File
@@ -42,4 +42,3 @@ jobs:
- bash: |
python -m pip install -r requirements-dev.txt pytest-azurepipelines
python ./bin/run_tests.py
+7 -2
View File
@@ -3,13 +3,18 @@
import os
import subprocess
import sys
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
subprocess.check_call([sys.executable, '-m', 'pytest', '-x', '--durations', '0', 'test'])
+1 -1
View File
@@ -1 +1 @@
__version__ = '1.4.2'
__version__ = '1.5.3'
+35 -29
View File
@@ -4,6 +4,7 @@ import sys
import textwrap
import traceback
from configparser import ConfigParser
from pathlib import Path
from typing import Any, Dict, List, Optional, overload
@@ -20,6 +21,7 @@ from cibuildwheel.util import (
BuildSelector,
DependencyConstraints,
Unbuffered,
resources_dir,
)
@@ -114,31 +116,44 @@ def main() -> None:
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_str = 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)
environment_config = get_option_from_environment('CIBW_ENVIRONMENT', platform=platform, default='')
before_test = get_option_from_environment('CIBW_BEFORE_TEST', platform=platform, 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_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')
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='')
build_selector = BuildSelector(build_config, skip_config)
try:
environment = parse_environment(environment_config)
except (EnvironmentParseError, ValueError):
print(f'cibuildwheel: Malformed environment option "{environment_config}"', file=sys.stderr)
traceback.print_exc(None, sys.stderr)
exit(2)
if dependency_versions == 'pinned':
dependency_constraints: Optional[DependencyConstraints] = DependencyConstraints.with_defaults()
elif dependency_versions == 'latest':
dependency_constraints = None
else:
dependency_constraints = DependencyConstraints(dependency_versions)
dependency_versions_path = Path(dependency_versions)
dependency_constraints = DependencyConstraints(dependency_versions_path)
if test_extras:
test_extras = f'[{test_extras}]'
@@ -148,21 +163,13 @@ def main() -> None:
except ValueError:
build_verbosity = 0
try:
environment = parse_environment(environment_config)
except (EnvironmentParseError, ValueError):
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)
# 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:
@@ -171,9 +178,7 @@ def main() -> None:
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.
@@ -206,12 +211,13 @@ def main() -> None:
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,
)
@@ -221,8 +227,8 @@ def main() -> None:
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)
+55 -23
View File
@@ -1,17 +1,24 @@
import shlex
import subprocess
from typing import Dict, NamedTuple
from typing import Callable, Dict, List, NamedTuple, Optional, Sequence
import bashlex # type: ignore
# a function that takes a shell command and the environment, and returns the result
EnvironmentExecutor = Callable[[str, Dict[str, str]], str]
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]) -> str:
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)
@@ -26,7 +33,7 @@ def evaluate(value: str, environment: Dict[str, str]) -> str:
return evaluate_node(
value_word_node,
context=NodeExecutionContext(environment=environment, input=value)
context=NodeExecutionContext(environment=environment, input=value, executor=executor or local_environment_executor)
)
@@ -34,40 +41,65 @@ 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(f'Unsupported bash construct: "{node.word}"')
raise ValueError(f'Unsupported bash construct: "{node.kind}"')
def evaluate_word_node(node: bashlex.ast.node, context: NodeExecutionContext) -> str:
word_start = node.pos[0]
word_end = node.pos[1]
word_string = context.input[word_start:word_end]
letters = list(word_string)
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] = ''
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(letters)
# apply bash-like quotes/whitespace treatment
return ' '.join(word.strip() for word in shlex.split(value))
return value
def evaluate_command_node(node: bashlex.ast.node, context: NodeExecutionContext) -> str:
words = [evaluate_node(part, context=context) for part in node.parts]
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: bashlex.ast.node, context: NodeExecutionContext) -> str:
+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))
+8 -6
View File
@@ -1,6 +1,6 @@
import bashlex # type: ignore
from typing import Dict, List
from typing import Dict, List, Mapping, Optional
from . import bashlex_eval
@@ -46,9 +46,9 @@ class EnvironmentAssignment:
self.name = name
self.value = value
def evaluated_value(self, environment: Dict[str, str]) -> str:
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) -> str:
return f'export {self.name}={self.value}'
@@ -61,11 +61,13 @@ class ParsedEnvironment:
def __init__(self, assignments: List[EnvironmentAssignment]):
self.assignments = assignments
def as_dictionary(self, prev_environment: Dict[str, str]) -> Dict[str, str]:
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
+136 -195
View File
@@ -1,26 +1,14 @@
import os
import platform
import shlex
import subprocess
import sys
import textwrap
import uuid
from os import PathLike
from pathlib import Path, PurePath
from typing import List, NamedTuple, Union
from typing import List, NamedTuple, Optional, Union
from .util import (
BuildOptions,
BuildSelector,
get_build_verbosity_extra_flags,
prepare_command,
)
def call(args: List[str], input: Optional[Union[str, bytes]] = None, universal_newlines: bool = False) -> None:
print('+ ' + ' '.join(shlex.quote(a) for a in args))
subprocess.run(
args, input=input, universal_newlines=universal_newlines, check=True
)
from .docker_container import DockerContainer
from .util import (BuildOptions, BuildSelector,
get_build_verbosity_extra_flags, prepare_command)
def matches_platform(identifier: str) -> bool:
@@ -47,37 +35,41 @@ def matches_platform(identifier: str) -> bool:
class PythonConfiguration(NamedTuple):
version: str
identifier: str
path: 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)]
@@ -104,194 +96,143 @@ def build(options: BuildOptions) -> None:
('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 = f'cibuildwheel-{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: str, error: Exception) -> None:
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('''
@@ -305,5 +246,5 @@ def troubleshoot(package_dir: str, error: Exception) -> None:
'''))
print(' Files detected:')
print('\n'.join([' ' + f for f in so_files]))
print('\n'.join([f' {f}' for f in so_files]))
print('')
+75 -63
View File
@@ -4,27 +4,22 @@ import shutil
import subprocess
import sys
import tempfile
from glob import glob
from typing import Dict, List, Optional, NamedTuple, Union
from os import PathLike
from pathlib import Path
from typing import Dict, List, NamedTuple, Optional, Sequence, Union
from .environment import ParsedEnvironment
from .util import (
BuildOptions,
BuildSelector,
download,
get_build_verbosity_extra_flags,
get_pip_script,
prepare_command,
)
from .util import (BuildOptions, BuildSelector, download,
get_build_verbosity_extra_flags, get_pip_script,
prepare_command)
def call(args: Union[str, List[str]], env: Optional[Dict[str, str]] = None, cwd: Optional[str] = None, shell: bool = False) -> int:
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(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)
@@ -41,8 +36,8 @@ def get_python_configurations(build_selector: BuildSelector) -> List[PythonConfi
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.3/python-3.8.3-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'),
@@ -52,41 +47,41 @@ def get_python_configurations(build_selector: BuildSelector) -> List[PythonConfi
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: str, python_executable: str, pip_executable: str) -> None:
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: str, url: str) -> str:
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 = 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 = 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, '/tmp/python-patch.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 = f'/Library/Frameworks/Python.framework/Versions/{version}/bin'
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)
@@ -94,16 +89,18 @@ def install_cpython(version: str, url: str) -> str:
return installation_bin_path
def install_pypy(version: str, url: str) -> str:
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)
@@ -111,7 +108,9 @@ def install_pypy(version: str, url: str) -> str:
return installation_bin_path
def setup_python(python_configuration: PythonConfiguration, dependency_constraint_flags: List[str], environment: ParsedEnvironment) -> Dict[str, str]:
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'):
@@ -121,8 +120,8 @@ def setup_python(python_configuration: PythonConfiguration, dependency_constrain
env = os.environ.copy()
env['PATH'] = os.pathsep.join([
SYMLINKS_DIR,
installation_bin_path,
str(SYMLINKS_DIR),
str(installation_bin_path),
env['PATH'],
])
@@ -144,15 +143,15 @@ def setup_python(python_configuration: PythonConfiguration, dependency_constrain
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
@@ -170,14 +169,19 @@ def setup_python(python_configuration: PythonConfiguration, dependency_constrain
def build(options: BuildOptions) -> None:
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')
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)
@@ -191,29 +195,39 @@ def build(options: BuildOptions) -> None:
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
@@ -221,10 +235,9 @@ def build(options: BuildOptions) -> None:
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)
@@ -234,7 +247,7 @@ def build(options: BuildOptions) -> None:
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:
@@ -245,8 +258,8 @@ def build(options: BuildOptions) -> None:
# 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)
@@ -254,5 +267,4 @@ def build(options: BuildOptions) -> None:
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)
+30 -29
View File
@@ -1,14 +1,15 @@
import os
import urllib.request
from fnmatch import fnmatch
from pathlib import Path
from time import sleep
from typing import Dict, List, NamedTuple, Optional
from typing import Dict, List, NamedTuple, Optional, Union
from .environment import ParsedEnvironment
def prepare_command(command: str, **kwargs: str) -> str:
def prepare_command(command: str, **kwargs: Union[str, os.PathLike]) -> str:
'''
Preprocesses a command by expanding variables like {python}.
@@ -58,11 +59,11 @@ class Unbuffered:
return getattr(self.stream, attr)
def download(url: str, dest: str) -> None:
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):
@@ -76,52 +77,52 @@ def download(url: str, dest: str) -> None:
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: str):
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()
@staticmethod
def with_defaults() -> 'DependencyConstraints':
return DependencyConstraints(
base_file_path=os.path.join(os.path.dirname(__file__), 'resources', 'constraints.txt')
base_file_path=resources_dir / 'constraints.txt'
)
def get_for_python_version(self, version: str) -> str:
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 + f'-python{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
class BuildOptions(NamedTuple):
package_dir: str
output_dir: str
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
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]]
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'
+124 -81
View File
@@ -3,29 +3,32 @@ import shutil
import subprocess
import sys
import tempfile
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
from typing import Dict, List, Optional, NamedTuple
import toml
from .environment import ParsedEnvironment
from .util import (
BuildOptions,
BuildSelector,
download,
get_build_verbosity_extra_flags,
get_pip_script,
prepare_command,
)
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: List[str], env: Optional[Dict[str, str]] = None, cwd: Optional[str] = None) -> int:
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 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]:
@@ -51,10 +54,10 @@ def get_python_configurations(build_selector: BuildSelector) -> List[PythonConfi
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.3', arch='32', identifier='cp38-win32', url=None),
PythonConfiguration(version='3.8.3', 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'),
@@ -71,36 +74,38 @@ def get_python_configurations(build_selector: BuildSelector) -> List[PythonConfi
return python_configurations
def extract_zip(zip_src: str, dest: str) -> None:
def extract_zip(zip_src: Path, dest: Path) -> None:
with ZipFile(zip_src) as zip:
zip.extractall(dest)
def install_cpython(version: str, arch: str, nuget: str) -> str:
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: str, arch: str, url: str) -> str:
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: PythonConfiguration, dependency_constraint_flags: List[str], environment: ParsedEnvironment) -> Dict[str, str]:
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'):
@@ -111,58 +116,88 @@ def setup_python(python_configuration: PythonConfiguration, dependency_constrain
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 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 = tempfile.mkdtemp(prefix='cibuildwheel')
built_wheel_dir = os.path.join(temp_dir, 'built_wheel')
repaired_wheel_dir = os.path.join(temp_dir, 'repaired_wheel')
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)
@@ -174,48 +209,59 @@ def build(options: BuildOptions) -> None:
# 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')
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(
@@ -223,30 +269,27 @@ def build(options: BuildOptions) -> None:
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)
+28 -2
View File
@@ -39,5 +39,31 @@ Forcing `distutils` or `setuptools` to use a more recent version of MSVC that su
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) and GitHub Actions has [an action `microsoft/setup-msbuild`](https://github.com/microsoft/setup-msbuild) that help setting up the Visual Studio environment.
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` enviroment (see previous step).
- 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.2
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.2
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.2
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.2
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.2
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.2
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.2
- 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.2
- 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.2
- python3 -m pip install cibuildwheel==1.5.3
script:
# build the wheels, put them into './wheelhouse'
+3 -3
View File
@@ -55,7 +55,7 @@ jobs:
- stage: deploy
name: Build and deploy Linux wheels
services: docker
install: python3 -m pip install cibuildwheel==1.4.2
install: python3 -m pip install cibuildwheel==1.5.3
script: python3 -m cibuildwheel --output-dir wheelhouse
after_success: |
python3 -m pip install twine
@@ -65,7 +65,7 @@ jobs:
name: Build and deploy macOS wheels
os: osx
language: shell
install: python3 -m pip install cibuildwheel==1.4.2
install: python3 -m pip install cibuildwheel==1.5.3
script: python3 -m cibuildwheel --output-dir wheelhouse
after_success: |
python3 -m pip install twine
@@ -75,7 +75,7 @@ jobs:
name: Build and deploy Windows wheels
os: windows
language: shell
install: python3 -m pip install cibuildwheel==1.4.2
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
+1 -1
View File
@@ -1,5 +1,5 @@
[flake8]
ignore = E501,W503,E741
ignore = E501,W503,E741,E226
application-import-names = cibuildwheel
exclude =
cibuildwheel/resources/,
+5 -7
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.2',
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',
+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)
+2 -5
View File
@@ -1,4 +1,3 @@
import os
import re
import pytest
import textwrap
@@ -39,11 +38,9 @@ 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
@@ -73,7 +70,7 @@ def test_pinned_versions(tmp_path, python_version):
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']:
+18 -5
View File
@@ -44,7 +44,7 @@ def test(tmp_path):
assert set(actual_wheels) == set(expected_wheels)
def test_overridden_path(tmp_path):
def test_overridden_path(tmp_path, capfd):
project_dir = tmp_path / 'project'
output_dir = tmp_path / 'output'
@@ -54,8 +54,21 @@ def test_overridden_path(tmp_path):
# mess up PATH, somehow
with pytest.raises(subprocess.CalledProcessError):
utils.cibuildwheel_run(project_dir, output_dir=output_dir, 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"''',
})
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
+49
View File
@@ -0,0 +1,49 @@
import textwrap
from . import test_projects
from . import utils
basic_project = test_projects.new_c_project(
setup_py_add=textwrap.dedent(
"""
# Will fail if PEP 518 does work
import sys
import requests
if sys.version_info < (3, 6, 0):
assert requests.__version__ == "2.22.0", "Requests found but wrong version ({0})".format(requests.__version__)
else:
assert requests.__version__ == "2.23.0", "Requests found but wrong version ({0})".format(requests.__version__)
# Just making sure environment is still set
import os
if os.environ.get("CIBUILDWHEEL", "0") != "1":
raise Exception("CIBUILDWHEEL environment variable is not set to 1")
"""
)
)
basic_project.files[
"pyproject.toml"
] = """
[build-system]
requires = [
"setuptools >= 42",
"wheel",
"requests==2.22.0; python_version<'3.6'",
"requests==2.23.0; python_version>='3.6'"
]
build-backend = "setuptools.build_meta"
"""
def test_pep518(tmp_path):
project_dir = tmp_path / "project"
basic_project.generate(project_dir)
# 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)
+5 -4
View File
@@ -1,8 +1,9 @@
from argparse import ArgumentParser
import importlib
import tempfile
import sys
import subprocess
import sys
import tempfile
from argparse import ArgumentParser
from pathlib import Path
def main():
@@ -19,7 +20,7 @@ def main():
project = getattr(importlib.import_module(module), name)
project_dir = tempfile.mkdtemp()
project_dir = Path(tempfile.mkdtemp())
project.generate(project_dir)
print('Project generated at', project_dir)
+9 -5
View File
@@ -1,5 +1,7 @@
import os
from pathlib import Path
import jinja2
from typing import Union, Dict, Any
@@ -14,6 +16,8 @@ class TestProject:
Write out to the filesystem using `generate`.
'''
__test__ = False # Have pytest ignore this class on `from .test_projects import TestProject`
files: FilesDict
template_context: TemplateContext
@@ -21,12 +25,12 @@ class TestProject:
self.files = {}
self.template_context = {}
def generate(self, path: str):
def generate(self, path: Path):
for filename, content in self.files.items():
file_path = os.path.join(path, filename)
os.makedirs(os.path.dirname(file_path), exist_ok=True)
file_path = path / filename
file_path.parent.mkdir(parents=True, exist_ok=True)
with open(file_path, 'w', encoding='utf8') as f:
with file_path.open('w', encoding='utf8') as f:
if isinstance(content, jinja2.Template):
content = content.render(self.template_context)
+2 -2
View File
@@ -1,4 +1,4 @@
import os
from pathlib import Path
import jinja2
@@ -35,7 +35,7 @@ def test(capfd, tmp_path):
project_dir = tmp_path / 'project'
subdir_package_project.generate(project_dir)
package_dir = os.path.join(project_dir, 'src', 'spam')
package_dir = Path('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',
+8 -5
View File
@@ -46,12 +46,15 @@ class TestSpam(TestCase):
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__")
# sys.prefix is different from sys.base_prefix when running a virtualenv
# See https://docs.python.org/3/library/venv.html, which virtualenv seems
# to honor in recent releases
# Python 2 doesn't have sys.base_prefix by default
if not hasattr(sys, 'base_prefix') or sys.prefix == sys.base_prefix:
self.fail("Not running in a virtualenv")
self.assertTrue(path_contains(virtualenv_path, sys.executable))
self.assertTrue(path_contains(virtualenv_path, spam.__file__))
self.assertTrue(path_contains(sys.prefix, sys.executable))
self.assertTrue(path_contains(sys.prefix, spam.__file__))
def test_uname(self):
if platform.system() == "Windows":
+3 -2
View File
@@ -10,9 +10,10 @@ import shutil
import subprocess
import sys
from contextlib import contextmanager
from pathlib import Path
from tempfile import mkdtemp
IS_WINDOWS_RUNNING_ON_AZURE = os.path.exists('C:\\hostedtoolcache')
IS_WINDOWS_RUNNING_ON_AZURE = Path('C:\\hostedtoolcache').exists()
IS_WINDOWS_RUNNING_ON_TRAVIS = os.environ.get('TRAVIS_OS_NAME') == 'windows'
@@ -66,7 +67,7 @@ def cibuildwheel_run(project_path, package_dir='.', env=None, add_env=None, outp
with TemporaryDirectoryIfNone(output_dir) as _output_dir:
subprocess.check_call(
[sys.executable, '-m', 'cibuildwheel', '--output-dir', str(_output_dir), package_dir],
[sys.executable, '-m', 'cibuildwheel', '--output-dir', str(_output_dir), str(package_dir)],
env=env,
cwd=project_path,
)
+21
View File
@@ -0,0 +1,21 @@
import pytest
def pytest_addoption(parser):
parser.addoption(
"--run-docker", action="store_true", default=False, help="run docker tests"
)
def pytest_configure(config):
config.addinivalue_line("markers", "docker: mark test requiring docker to run")
def pytest_collection_modifyitems(config, items):
if config.getoption("--run-docker"):
# --run-docker given in cli: do not skip docker tests
return
skip_docker = pytest.mark.skip(reason="need --run-docker option to run")
for item in items:
if "docker" in item.keywords:
item.add_marker(skip_docker)
+9 -23
View File
@@ -1,30 +1,16 @@
from cibuildwheel.util import DependencyConstraints
import os
from pathlib import Path
def test_defaults():
dependency_constraints = DependencyConstraints.with_defaults()
project_root = os.path.dirname(os.path.dirname(__file__))
resources_dir = os.path.join(project_root, 'cibuildwheel', 'resources')
project_root = Path(__file__).parents[1]
resources_dir = project_root / 'cibuildwheel' / 'resources'
assert os.path.samefile(
dependency_constraints.base_file_path,
os.path.join(resources_dir, 'constraints.txt')
)
assert os.path.samefile(
dependency_constraints.get_for_python_version('3.8'),
os.path.join(resources_dir, 'constraints.txt')
)
assert os.path.samefile(
dependency_constraints.get_for_python_version('3.6'),
os.path.join(resources_dir, 'constraints-python36.txt')
)
assert os.path.samefile(
dependency_constraints.get_for_python_version('3.5'),
os.path.join(resources_dir, 'constraints-python35.txt')
)
assert os.path.samefile(
dependency_constraints.get_for_python_version('2.7'),
os.path.join(resources_dir, 'constraints-python27.txt')
)
assert dependency_constraints.base_file_path.samefile(resources_dir / 'constraints.txt')
assert dependency_constraints.get_for_python_version('3.8').samefile(resources_dir / 'constraints.txt')
assert dependency_constraints.get_for_python_version('3.6').samefile(resources_dir / 'constraints-python36.txt')
assert dependency_constraints.get_for_python_version('3.5').samefile(resources_dir / 'constraints-python35.txt')
assert dependency_constraints.get_for_python_version('2.7').samefile(resources_dir / 'constraints-python27.txt')
+147
View File
@@ -0,0 +1,147 @@
import platform
import random
import shutil
import subprocess
import textwrap
from pathlib import Path, PurePath
import pytest
from cibuildwheel.docker_container import DockerContainer
# for these tests we use manylinux2014 images, because they're available on
# multi architectures and include python3.8
pm = platform.machine()
if pm == "x86_64":
DEFAULT_IMAGE = 'quay.io/pypa/manylinux2014_x86_64:2020-05-17-2f8ac3b'
elif pm == "aarch64":
DEFAULT_IMAGE = 'quay.io/pypa/manylinux2014_aarch64:2020-05-17-2f8ac3b'
elif pm == "ppc64le":
DEFAULT_IMAGE = 'quay.io/pypa/manylinux2014_ppc64le:2020-05-17-2f8ac3b'
elif pm == "s390x":
DEFAULT_IMAGE = 'quay.io/pypa/manylinux2014_s390x:2020-05-17-2f8ac3b'
@pytest.mark.docker
def test_simple():
with DockerContainer(DEFAULT_IMAGE) as container:
assert container.call(['echo', 'hello'], capture_output=True) == 'hello\n'
@pytest.mark.docker
def test_no_lf():
with DockerContainer(DEFAULT_IMAGE) as container:
assert container.call(['printf', 'hello'], capture_output=True) == 'hello'
@pytest.mark.docker
def test_environment():
with DockerContainer(DEFAULT_IMAGE) as container:
assert container.call(['sh', '-c', 'echo $TEST_VAR'], env={'TEST_VAR': '1'}, capture_output=True) == '1\n'
@pytest.mark.docker
def test_container_removed():
with DockerContainer(DEFAULT_IMAGE) as container:
docker_containers_listing = subprocess.run('docker container ls', shell=True, check=True, stdout=subprocess.PIPE, universal_newlines=True).stdout
assert container.name in docker_containers_listing
old_container_name = container.name
docker_containers_listing = subprocess.run('docker container ls', shell=True, check=True, stdout=subprocess.PIPE, universal_newlines=True).stdout
assert old_container_name not in docker_containers_listing
@pytest.mark.docker
def test_large_environment():
# max environment variable size is 128kB
long_env_var_length = 127*1024
large_environment = {
'a': '0'*long_env_var_length,
'b': '0'*long_env_var_length,
'c': '0'*long_env_var_length,
'd': '0'*long_env_var_length,
}
with DockerContainer(DEFAULT_IMAGE) as container:
# check the length of d
assert container.call(['sh', '-c', 'echo ${#d}'], env=large_environment, capture_output=True) == f'{long_env_var_length}\n'
@pytest.mark.docker
def test_binary_output():
with DockerContainer(DEFAULT_IMAGE) as container:
# note: the below embedded snippets are in python2
# check that we can pass though arbitrary binary data without erroring
container.call(['/usr/bin/python2', '-c', textwrap.dedent('''
import sys
sys.stdout.write(''.join(chr(n) for n in range(0, 256)))
''')])
# check that we can capture arbitrary binary data
output = container.call(['/usr/bin/python2', '-c', textwrap.dedent('''
import sys
sys.stdout.write(''.join(chr(n % 256) for n in range(0, 512)))
''')], capture_output=True)
data = bytes(output, encoding='utf8', errors='surrogateescape')
for i in range(0, 512):
assert data[i] == i % 256
# check that environment variables can carry binary data, except null characters
# (https://www.gnu.org/software/libc/manual/html_node/Environment-Variables.html)
binary_data = bytes(n for n in range(1, 256))
binary_data_string = str(binary_data, encoding='utf8', errors='surrogateescape')
output = container.call(
['python2', '-c', 'import os, sys; sys.stdout.write(os.environ["TEST_VAR"])'],
env={'TEST_VAR': binary_data_string},
capture_output=True,
)
assert output == binary_data_string
@pytest.mark.docker
def test_file_operations(tmp_path: Path):
with DockerContainer(DEFAULT_IMAGE) as container:
# test copying a file in
test_binary_data = bytes(random.randrange(256) for _ in range(1000))
original_test_file = tmp_path / 'test.dat'
original_test_file.write_bytes(test_binary_data)
dst_file = PurePath('/tmp/test.dat')
container.copy_into(original_test_file, dst_file)
output = container.call(['cat', dst_file], capture_output=True)
assert test_binary_data == bytes(output, encoding='utf8', errors='surrogateescape')
@pytest.mark.docker
def test_dir_operations(tmp_path: Path):
with DockerContainer(DEFAULT_IMAGE) as container:
test_binary_data = bytes(random.randrange(256) for _ in range(1000))
original_test_file = tmp_path / 'test.dat'
original_test_file.write_bytes(test_binary_data)
# test copying a dir in
test_dir = tmp_path / 'test_dir'
test_dir.mkdir()
test_file = test_dir / 'test.dat'
shutil.copyfile(original_test_file, test_file)
dst_dir = PurePath('/tmp/test_dir')
dst_file = dst_dir / 'test.dat'
container.copy_into(test_dir, dst_dir)
output = container.call(['cat', dst_file], capture_output=True)
assert test_binary_data == bytes(output, encoding='utf8', errors='surrogateescape')
# test glob
assert container.glob(dst_dir, '*.dat') == [dst_file]
# test copy dir out
new_test_dir = tmp_path / 'test_dir_new'
container.copy_out(dst_dir, new_test_dir)
assert test_binary_data == (new_test_dir / 'test.dat').read_bytes()
+29
View File
@@ -94,3 +94,32 @@ def test_no_vars_pass_through():
environment_dict = environment_recipe.as_dictionary(prev_environment={'CIBUILDWHEEL': 'awesome'})
assert environment_dict == {'CIBUILDWHEEL': 'awesome'}
def test_operators_inside_eval():
environment_recipe = parse_environment('SOMETHING="$(echo a; echo b; echo c)"')
# pass the existing process env so PATH is available
environment_dict = environment_recipe.as_dictionary(os.environ.copy())
assert environment_dict.get('SOMETHING') == 'a\nb\nc'
def test_substitution_with_backslash():
environment_recipe = parse_environment('PATH2="somewhere_else;$PATH1"')
# pass the existing process env so PATH is available
environment_dict = environment_recipe.as_dictionary(prev_environment={
'PATH1': 'c:\\folder\\'
})
assert environment_dict.get('PATH2') == 'somewhere_else;c:\\folder\\'
def test_awkwardly_quoted_variable():
environment_recipe = parse_environment('VAR2=something"like this""$VAR1"$VAR1$(echo "theres more")"$(echo "and more!")"')
# pass the existing process env so PATH is available
environment_dict = environment_recipe.as_dictionary({'VAR1': 'but wait'})
assert environment_dict.get('VAR2') == 'somethinglike thisbut waitbut waittheres moreand more!'
+13 -8
View File
@@ -1,6 +1,6 @@
import os
import subprocess
import sys
from pathlib import Path
import pytest
@@ -18,7 +18,7 @@ class ArgsInterceptor:
self.kwargs = kwargs
MOCK_PACKAGE_DIR = 'some_package_dir'
MOCK_PACKAGE_DIR = Path('some_package_dir')
@pytest.fixture(autouse=True)
@@ -31,28 +31,33 @@ def mock_protection(monkeypatch):
def fail_on_call(*args, **kwargs):
raise RuntimeError("This should never be called")
def ignore_call(*args, **kwargs):
pass
monkeypatch.setattr(subprocess, 'Popen', fail_on_call)
monkeypatch.setattr(util, 'download', fail_on_call)
monkeypatch.setattr(windows, 'build', fail_on_call)
monkeypatch.setattr(linux, 'build', fail_on_call)
monkeypatch.setattr(macos, 'build', fail_on_call)
monkeypatch.setattr(Path, 'mkdir', ignore_call)
@pytest.fixture(autouse=True)
def fake_package_dir(monkeypatch):
'''
Monkey-patch enough for the main() function to run
'''
real_os_path_exists = os.path.exists
real_path_exists = Path.exists
def mock_os_path_exists(path):
if path == os.path.join(MOCK_PACKAGE_DIR, 'setup.py'):
def mock_path_exists(path):
if path == MOCK_PACKAGE_DIR / 'setup.py':
return True
else:
return real_os_path_exists(path)
return real_path_exists(path)
monkeypatch.setattr(os.path, 'exists', mock_os_path_exists)
monkeypatch.setattr(sys, 'argv', ['cibuildwheel', MOCK_PACKAGE_DIR])
monkeypatch.setattr(Path, 'exists', mock_path_exists)
monkeypatch.setattr(sys, 'argv', ['cibuildwheel', str(MOCK_PACKAGE_DIR)])
@pytest.fixture(params=['linux', 'macos', 'windows'])
+24 -5
View File
@@ -1,5 +1,6 @@
import sys
from fnmatch import fnmatch
from pathlib import Path
import pytest
@@ -12,9 +13,9 @@ from cibuildwheel.util import BuildSelector
def test_output_dir(platform, intercepted_build_args, monkeypatch):
OUTPUT_DIR = 'some_output_dir'
OUTPUT_DIR = Path('some_output_dir')
monkeypatch.setenv('CIBW_OUTPUT_DIR', OUTPUT_DIR)
monkeypatch.setenv('CIBW_OUTPUT_DIR', str(OUTPUT_DIR))
main()
@@ -24,14 +25,14 @@ def test_output_dir(platform, intercepted_build_args, monkeypatch):
def test_output_dir_default(platform, intercepted_build_args, monkeypatch):
main()
assert intercepted_build_args.args[0].output_dir == 'wheelhouse'
assert intercepted_build_args.args[0].output_dir == Path('wheelhouse')
@pytest.mark.parametrize('also_set_environment', [False, True])
def test_output_dir_argument(also_set_environment, platform, intercepted_build_args, monkeypatch):
OUTPUT_DIR = 'some_output_dir'
OUTPUT_DIR = Path('some_output_dir')
monkeypatch.setattr(sys, 'argv', sys.argv + ['--output-dir', OUTPUT_DIR])
monkeypatch.setattr(sys, 'argv', sys.argv + ['--output-dir', str(OUTPUT_DIR)])
if also_set_environment:
monkeypatch.setenv('CIBW_OUTPUT_DIR', 'not_this_output_dir')
@@ -232,3 +233,21 @@ def test_build_selector_migrations(intercepted_build_args, monkeypatch, option_n
assert intercepted_build_selector.build_patterns == build_selector_patterns
else:
assert intercepted_build_selector.skip_patterns == build_selector_patterns
@pytest.mark.parametrize('before_all', ["", None, 'test text'])
@pytest.mark.parametrize('platform_specific', [False, True])
def test_before_all(before_all, platform_specific, platform, intercepted_build_args, monkeypatch):
if before_all is not None:
if platform_specific:
monkeypatch.setenv('CIBW_BEFORE_ALL_' + platform.upper(), before_all)
monkeypatch.setenv('CIBW_BEFORE_ALL', 'overwritten')
else:
monkeypatch.setenv('CIBW_BEFORE_ALL', before_all)
main()
if before_all is None:
before_all = ""
assert intercepted_build_args.args[0].before_all == before_all