Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dbb9bcb4d5 | ||
|
|
c585dbd6f3 | ||
|
|
fdda1ee2e1 | ||
|
|
533ef04740 | ||
|
|
8a3935551b | ||
|
|
1b33c4cac5 | ||
|
|
f734bffc1e | ||
|
|
b40852ec90 | ||
|
|
a232b09fee | ||
|
|
e3b2de0489 | ||
|
|
c3565d5025 | ||
|
|
31ac2ba03f | ||
|
|
083ece8bbc | ||
|
|
bdbbf18e43 | ||
|
|
45a7836cc6 | ||
|
|
d5fa5475d4 | ||
|
|
3f6a943b6d | ||
|
|
c7764d239b | ||
|
|
e072e82252 | ||
|
|
cf7f2e18ce | ||
|
|
a2a2a7b382 | ||
|
|
ba22589f6f | ||
|
|
e9bf990fd2 | ||
|
|
db09cfc4a9 | ||
|
|
2c0d6e3db1 | ||
|
|
89c8735719 | ||
|
|
7a2e951f48 | ||
|
|
ff942ad766 | ||
|
|
881cd66fae | ||
|
|
278661df77 | ||
|
|
520daa4c23 | ||
|
|
2679cb7f46 | ||
|
|
56f9a37165 | ||
|
|
5c91340ad0 | ||
|
|
c53d79557e | ||
|
|
510229177a | ||
|
|
46868f6cfe | ||
|
|
61138c19d5 | ||
|
|
782e383c10 | ||
|
|
fe3038049a | ||
|
|
17a2485d25 | ||
|
|
747556c417 | ||
|
|
21c53fed74 | ||
|
|
c230a6d3ce | ||
|
|
32eb1b2dee | ||
|
|
3372e585f9 | ||
|
|
309a2adcda | ||
|
|
095a857dcf | ||
|
|
c06cffc652 |
+17
@@ -0,0 +1,17 @@
|
||||
matrix:
|
||||
include:
|
||||
- sudo: required
|
||||
services:
|
||||
- docker
|
||||
- os: osx
|
||||
|
||||
script:
|
||||
- |
|
||||
if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
|
||||
pip install .
|
||||
python ./run_tests.py
|
||||
else
|
||||
# linux test requires root to clean up the wheelhouse (docker runs as root)
|
||||
sudo pip install .
|
||||
sudo python ./run_tests.py
|
||||
fi
|
||||
@@ -1,6 +1,6 @@
|
||||
This project is licensed under the 'BSD 2-clause license'.
|
||||
|
||||
Copyright (c) 2016, Tingbot. All rights reserved.
|
||||
Copyright (c) 2017, Joe Rickerby. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
cibuildwheel
|
||||
============
|
||||
|
||||
[](https://pypi.python.org/pypi/cibuildwheel) [](https://travis-ci.org/joerick/cibuildwheel) [](https://ci.appveyor.com/project/joerick/cibuildwheel/branch/master)
|
||||
|
||||
Python wheels are great. Building them across **Mac, Linux, Windows**, on **multiple versions of Python**, is not.
|
||||
|
||||
`cibuildwheel` is here to help. `cibuildwheel` runs on your CI server - currently it supports Travis CI and Appveyor - and it builds and tests your wheels across all of your platforms.
|
||||
@@ -18,7 +20,7 @@ What does it do?
|
||||
| Python 3.5 | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| Python 3.6 | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
|
||||
- Builds manylinux, macOS and Windows (32 and 64bit) wheels
|
||||
- Builds manylinux, macOS and Windows (32 and 64bit) wheels using Travis CI and Appveyor
|
||||
- Bundles shared library dependencies on Linux and macOS through [auditwheel](https://github.com/pypa/auditwheel) and [delocate](https://github.com/matthew-brett/delocate)
|
||||
- Runs the library test suite against the wheel-installed version of your library
|
||||
|
||||
@@ -42,7 +44,7 @@ Usage
|
||||
- os: osx
|
||||
|
||||
script:
|
||||
- pip install cibuildwheel
|
||||
- pip install cibuildwheel==0.3.0
|
||||
- cibuildwheel --output-dir wheelhouse
|
||||
```
|
||||
|
||||
@@ -52,7 +54,7 @@ Usage
|
||||
|
||||
```
|
||||
build_script:
|
||||
- pip install cibuildwheel
|
||||
- pip install cibuildwheel==0.3.0
|
||||
- cibuildwheel --output-dir wheelhouse
|
||||
artifacts:
|
||||
- path: "wheelhouse\\*.whl"
|
||||
@@ -130,24 +132,38 @@ Space-separated list of dependencies required for running the tests.
|
||||
Example: `pytest`
|
||||
Example: `nose==1.3.7 moto==0.4.31`
|
||||
|
||||
| Environment variable: `CIBW_SKIP` | 🔶 coming soon 🔶
|
||||
| --- | ---
|
||||
| Environment variable: `CIBW_BEFORE_BUILD`
|
||||
| ---
|
||||
|
||||
Optional.
|
||||
|
||||
Space-separated list of builds to skip. Each build has an identifier like `cp27-linux_x86_64` or `cp34-macosx_10_6_intel` - you can list ones to skip here and `cibuildwheel` won't try to build them.
|
||||
Shell command to run before building the wheel. This option allows you to run a command in **each** Python environment before the `pip wheel` command. This is useful if you need to set up some dependency so it's available during the build.
|
||||
|
||||
The active Python binary can be accessed using `{python}`, and pip with `{pip}`. These are useful when you need to write `python3` or `pip3` on a Python 3.x build.
|
||||
|
||||
Example: `{pip} install pybind11`
|
||||
|
||||
Platform-specific variants also available:
|
||||
`CIBW_BEFORE_BUILD_MACOS` | `CIBW_BEFORE_BUILD_WINDOWS` | `CIBW_BEFORE_BUILD_LINUX`
|
||||
|
||||
| Environment variable: `CIBW_SKIP`
|
||||
| ---
|
||||
|
||||
Optional.
|
||||
|
||||
Space-separated list of builds to skip. Each build has an identifier like `cp27-manylinux1_x86_64` or `cp34-macosx_10_6_intel` - you can list ones to skip here and `cibuildwheel` won't try to build them.
|
||||
|
||||
The format is `python_tag-platform_tag`. The tags are as defined in [PEP 0425](https://www.python.org/dev/peps/pep-0425/#details).
|
||||
|
||||
Python tags look like `cp27` `cp34` `cp35` `cp36`
|
||||
|
||||
Platform tags look like `macosx_10_6_intel` `linux_x86_64` `linux_i386` `win32` `win_amd64`
|
||||
Platform tags look like `macosx_10_6_intel` `manylinux1_x86_64` `manylinux1_i386` `win32` `win_amd64`
|
||||
|
||||
You can also use shell-style globbing syntax (as per `fnmatch`)
|
||||
|
||||
Example: `cp27-macosx_10_6_intel ` (don't build on Python 2 on Mac)
|
||||
Example: `cp27-win*` (don't build on Python 2.7 on Windows)
|
||||
Example: `cp34-*` (don't build on Python 3.4)
|
||||
Example: `cp34-* cp35-*` (don't build on Python 3.4 or Python 3.5)
|
||||
|
||||
--
|
||||
|
||||
@@ -166,20 +182,86 @@ Example: `cp34-*` (don't build on Python 3.4)
|
||||
</code></pre></td>
|
||||
</tr></table>
|
||||
|
||||
Delivering to PyPI
|
||||
------------------
|
||||
|
||||
After you've built your wheels, you'll probably want to deliver them to PyPI.
|
||||
|
||||
### Manual method
|
||||
|
||||
On your development machine, do the following...
|
||||
|
||||
```bash
|
||||
# Clear out your 'dist' folder.
|
||||
rm -rf dist
|
||||
# Make a source distribution
|
||||
python setup.py sdist
|
||||
|
||||
# 🏃🏻
|
||||
# Go and download your wheel files from wherever you put them. Put
|
||||
# them all into the 'dist' folder.
|
||||
|
||||
# Upload using 'twine' (you may need to 'pip install twine')
|
||||
twine upload dist/*
|
||||
```
|
||||
|
||||
### Semi-automatic method using wheelhouse-uploader
|
||||
|
||||
Obviously, manual steps are for chumps, so we can automate this a little by using [wheelhouse-uploader](https://github.com/ogrisel/wheelhouse-uploader).
|
||||
|
||||
> Quick note from me - using S3 as a storage didn't work due to a [bug](https://issues.apache.org/jira/browse/LIBCLOUD-792) in libcloud. Feel free to use my fork of that package that fixes the bug `pip install https://github.com/joerick/libcloud/archive/v1.5.0-s3fix.zip`
|
||||
|
||||
### Automatic method
|
||||
|
||||
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 any cloud 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.
|
||||
|
||||
It didn't work!
|
||||
---------------
|
||||
|
||||
If your wheel didn't compile, check the list below for some debugging tips.
|
||||
|
||||
- A mistake in your config. To quickly test your config without doing a git push and waiting for your code to build on CI, you can run the Linux build in a Docker container. Try `cibuildwheel --platform linux`. You'll have to bring your config into the current environment first.
|
||||
- A mistake in your config. To quickly test your config without doing a git push and waiting for your code to build on CI, you can run the Linux build in a Docker container. On Mac or Linux, with Docker running, try `cibuildwheel --platform linux`. You'll have to bring your config into the current environment first.
|
||||
- Missing dependency. You might need to install something on the build machine. You can do this in `.travis.yml` or `appveyor.yml`, with apt-get, brew or whatever Windows uses :P . Given how the Linux build works, we'll probably have to build something into `cibuildwheel`. Let's chat about that over in the issues!
|
||||
- Windows: missing C feature. The Windows C compiler doesn't support C language features invented after 1990, so you'll have to backport your C code to C90. For me, this mostly involved putting my variable declarations at the top of the function like an animal.
|
||||
|
||||
Working examples
|
||||
----------------
|
||||
|
||||
Here are some repos that use cibuildwheel.
|
||||
|
||||
- [pyinstrument_cext](https://github.com/joerick/pyinstrument_cext)
|
||||
|
||||
> Add repo here! Send a PR.
|
||||
|
||||
Legal note
|
||||
----------
|
||||
|
||||
Since `cibuildwheel` runs the wheel through delocate or auditwheel, it will automatically bundle library dependencies. This is similar to static linking - it might have some licence implications. Check the license for any code you're pulling in to make sure that's allowed.
|
||||
|
||||
Changelog
|
||||
=========
|
||||
|
||||
### 0.2.1
|
||||
|
||||
11 June 2017
|
||||
|
||||
- Changed the build process to install the package before building the wheel - this allows direct dependencies to be installed first (#9, thanks @tgarc!)
|
||||
- Added Python 3 support for the main process, for systems where Python 3 is the default (#8, thanks @tgarc).
|
||||
|
||||
### 0.2.0
|
||||
|
||||
13 April 2017
|
||||
|
||||
- Added `CIBW_SKIP` option, letting users explicitly skip a build
|
||||
- Added `CIBW_BEFORE_BUILD` option, letting users run a shell command before the build starts
|
||||
|
||||
### 0.1.3
|
||||
|
||||
31 March 2017
|
||||
|
||||
- First public release!
|
||||
|
||||
Contributing
|
||||
============
|
||||
@@ -211,6 +293,7 @@ Credits
|
||||
- ⭐️ @matthew-brett for [matthew-brett/multibuild](http://github.com/matthew-brett/multibuild) and [matthew-brett/delocate](http://github.com/matthew-brett/delocate)
|
||||
- @PyPA for the manylinux Docker images [pypa/manylinux](https://github.com/pypa/manylinux)
|
||||
- @ogrisel for [wheelhouse-uploader](https://github.com/ogrisel/wheelhouse-uploader) and `run_with_env.cmd`
|
||||
- @zfrenchee for [help debugging many issues](https://github.com/joerick/cibuildwheel/issues/2)
|
||||
|
||||
See also
|
||||
--------
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
build_script:
|
||||
- pip install .
|
||||
- python ./run_tests.py
|
||||
-292
@@ -1,292 +0,0 @@
|
||||
from __future__ import print_function
|
||||
import argparse, os, subprocess, sys, tempfile, shlex
|
||||
from collections import namedtuple
|
||||
from glob import glob
|
||||
|
||||
try:
|
||||
# Python 3
|
||||
import urllib.request as urllib2
|
||||
except ImportError:
|
||||
# Python 2
|
||||
import urllib2
|
||||
|
||||
try:
|
||||
from shlex import quote as shlex_quote
|
||||
except ImportError:
|
||||
from pipes import quote as shlex_quote
|
||||
|
||||
|
||||
def linux_build(project_dir, package_name, output_dir, test_command, test_requires):
|
||||
for docker_image in ['quay.io/pypa/manylinux1_x86_64', 'quay.io/pypa/manylinux1_i686']:
|
||||
bash_script = '''
|
||||
set -o errexit
|
||||
set -o xtrace
|
||||
cd /project
|
||||
|
||||
for PYBIN in /opt/python/*/bin; do
|
||||
"$PYBIN/pip" wheel . -w /tmp/linux_wheels
|
||||
done
|
||||
|
||||
for whl in /tmp/linux_wheels/*.whl; do
|
||||
auditwheel repair "$whl" -w /output
|
||||
done
|
||||
|
||||
# Install packages and test
|
||||
for PYBIN in /opt/python/*/bin/; do
|
||||
# Install the wheel we just built
|
||||
"$PYBIN/pip" install {package_name} --no-index -f /output
|
||||
|
||||
# Install any requirements to run the tests
|
||||
if [ ! -z "{test_requires}" ]; then
|
||||
"$PYBIN/pip" install {test_requires}
|
||||
fi
|
||||
|
||||
# Run the tests from a different directory
|
||||
if [ ! -z {test_command} ]; then
|
||||
(cd "$HOME" && export PATH=$PYBIN:$PATH && sh -c {test_command})
|
||||
fi
|
||||
done
|
||||
'''.format(
|
||||
package_name=package_name,
|
||||
test_requires=' '.join(test_requires),
|
||||
test_command=shlex_quote(test_command.format(project='/project') if test_command else ''),
|
||||
)
|
||||
|
||||
docker_process = subprocess.Popen([
|
||||
'docker',
|
||||
'run',
|
||||
'--rm',
|
||||
'-i',
|
||||
'-v', '%s:/project' % os.path.abspath(project_dir),
|
||||
'-v', '%s:/output' % os.path.abspath(output_dir),
|
||||
docker_image,
|
||||
'/bin/bash'],
|
||||
stdin=subprocess.PIPE)
|
||||
|
||||
docker_process.communicate(bash_script)
|
||||
|
||||
if docker_process.returncode != 0:
|
||||
exit(1)
|
||||
|
||||
|
||||
def windows_build(project_dir, package_name, output_dir, test_command, test_requires):
|
||||
# run_with_env is a cmd file that sets the right environment variables to
|
||||
run_with_env = os.path.join(tempfile.gettempdir(), 'appveyor_run_with_env.cmd')
|
||||
if not os.path.exists(run_with_env):
|
||||
with open(run_with_env, 'wb') as f:
|
||||
request = urllib2.urlopen('https://github.com/ogrisel/python-appveyor-demo/raw/09a1c8672e5015a74d8f69d07add6ee803c176ec/appveyor/run_with_env.cmd')
|
||||
f.write(request.read())
|
||||
|
||||
def shell(args, env=None, cwd=None):
|
||||
# print the command executing for the logs
|
||||
print('+ ' + ' '.join(args))
|
||||
args = ['cmd', '/E:ON', '/V:ON', '/C', run_with_env] + args
|
||||
return subprocess.check_call(' '.join(args), env=env, cwd=cwd)
|
||||
|
||||
PythonConfiguration = namedtuple('PythonConfiguration', ['version', 'arch', 'path'])
|
||||
python_configurations = [
|
||||
PythonConfiguration(version='2.7.x', arch="32", path='C:\Python27'),
|
||||
PythonConfiguration(version='2.7.x', arch="64", path='C:\Python27-x64'),
|
||||
PythonConfiguration(version='3.3.x', arch="32", path='C:\Python33'),
|
||||
PythonConfiguration(version='3.3.x', arch="64", path='C:\Python33-x64'),
|
||||
PythonConfiguration(version='3.4.x', arch="32", path='C:\Python34'),
|
||||
PythonConfiguration(version='3.4.x', arch="64", path='C:\Python34-x64'),
|
||||
PythonConfiguration(version='3.5.x', arch="32", path='C:\Python35'),
|
||||
PythonConfiguration(version='3.5.x', arch="64", path='C:\Python35-x64'),
|
||||
PythonConfiguration(version='3.6.x', arch="32", path='C:\Python36'),
|
||||
PythonConfiguration(version='3.6.x', arch="64", path='C:\Python36-x64'),
|
||||
]
|
||||
|
||||
for config in python_configurations:
|
||||
env = os.environ.copy()
|
||||
# set up environment variables for run_with_env
|
||||
env['PYTHON_VERSION'] = config.version
|
||||
env['PYTHON_ARCH'] = config.arch
|
||||
env['PATH'] = os.pathsep.join([
|
||||
config.path,
|
||||
os.path.join(config.path, 'Scripts'),
|
||||
env['PATH']
|
||||
])
|
||||
|
||||
# for the logs - check we're running the right version of python
|
||||
shell(['python', '--version'], env=env)
|
||||
shell(['python', '-c', '"import struct; print(struct.calcsize(\'P\') * 8)\"'], env=env)
|
||||
|
||||
# prepare the Python environment
|
||||
shell(['pip', 'install', '--disable-pip-version-check', '--user', '--upgrade', 'pip'], env=env)
|
||||
shell(['pip', 'install', 'wheel'], env=env)
|
||||
|
||||
# build the wheel
|
||||
shell(['pip', 'wheel', project_dir, '-w', output_dir], env=env)
|
||||
|
||||
# install the wheel
|
||||
shell(['pip', 'install', package_name, '--no-index', '-f', output_dir], env=env)
|
||||
|
||||
# test the wheel
|
||||
if test_requires:
|
||||
shell(['pip', 'install'] + test_requires, env=env)
|
||||
if test_command:
|
||||
# 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)
|
||||
abs_project_dir = os.path.abspath(project_dir)
|
||||
test_command_absolute = test_command.format(project=abs_project_dir)
|
||||
shell([test_command_absolute], cwd='c:\\', env=env)
|
||||
|
||||
|
||||
def macos_build(project_dir, package_name, output_dir, test_command, test_requires):
|
||||
PythonConfiguration = namedtuple('PythonConfiguration', ['version', 'url'])
|
||||
python_configurations = [
|
||||
PythonConfiguration(version='2.7', url='https://www.python.org/ftp/python/2.7.13/python-2.7.13-macosx10.6.pkg'),
|
||||
PythonConfiguration(version='3.4', url='https://www.python.org/ftp/python/3.4.4/python-3.4.4-macosx10.6.pkg'),
|
||||
PythonConfiguration(version='3.5', url='https://www.python.org/ftp/python/3.5.3/python-3.5.3-macosx10.6.pkg'),
|
||||
PythonConfiguration(version='3.6', url='https://www.python.org/ftp/python/3.6.0/python-3.6.0-macosx10.6.pkg'),
|
||||
]
|
||||
|
||||
def shell(args, env=None, cwd=None):
|
||||
# print the command executing for the logs
|
||||
print('+ ' + ' '.join(shlex_quote(a) for a in args))
|
||||
return subprocess.check_call(args, env=env, cwd=cwd)
|
||||
|
||||
for config in python_configurations:
|
||||
# download the pkg
|
||||
shell(['curl', '-L', '-o', '/tmp/Python.pkg', config.url])
|
||||
# install
|
||||
shell(['sudo', 'installer', '-pkg', '/tmp/Python.pkg', '-target', '/'])
|
||||
|
||||
env = os.environ.copy()
|
||||
env['PATH'] = os.pathsep.join([
|
||||
'/Library/Frameworks/Python.framework/Versions/%s/bin' % config.version,
|
||||
env['PATH'],
|
||||
])
|
||||
|
||||
python = 'python3' if config.version[0] == '3' else 'python2'
|
||||
pip = 'pip3' if config.version[0] == '3' else 'pip2'
|
||||
|
||||
# check what version we're on
|
||||
shell(['which', python], env=env)
|
||||
shell([python, '--version'], env=env)
|
||||
|
||||
# install pip & wheel
|
||||
shell([python, '-m', 'ensurepip', '--upgrade'], env=env)
|
||||
shell(['which', pip], env=env) # todo: remove
|
||||
shell([pip, '--version'])
|
||||
shell([pip, 'install', 'wheel'], env=env)
|
||||
shell([pip, 'install', 'delocate'], env=env)
|
||||
|
||||
# build the wheel to temp dir
|
||||
temp_wheel_dir = '/tmp/tmpwheel%s' % config.version
|
||||
shell([pip, 'wheel', project_dir, '-w', temp_wheel_dir], env=env)
|
||||
temp_wheel = glob(temp_wheel_dir+'/*.whl')[0]
|
||||
|
||||
# list the dependencies
|
||||
shell(['delocate-listdeps', temp_wheel], env=env)
|
||||
# rebuild the wheel with shared libraries included and place in output dir
|
||||
shell(['delocate-wheel', '-w', output_dir, temp_wheel], env=env)
|
||||
|
||||
# install the wheel
|
||||
shell([pip, 'install', package_name, '--no-index', '--find-links', output_dir], env=env)
|
||||
|
||||
# test the wheel
|
||||
if test_requires:
|
||||
shell([pip, '-v', 'install'] + test_requires, env=env)
|
||||
if test_command:
|
||||
# run the tests from $HOME, with an absolute path in the command
|
||||
# (this ensures that Python runs the tests against the installed wheel
|
||||
# and not the repo code)
|
||||
abs_project_dir = os.path.abspath(project_dir)
|
||||
test_command_absolute = test_command.format(project=abs_project_dir)
|
||||
shell(['which', 'nosetests'], env=env) # todo: remove
|
||||
shell(['which', 'nosetests'], cwd=os.environ['HOME'], env=env) # todo: remove
|
||||
shell(['nosetests', '-v'], env=env) # todo: remove
|
||||
shell(['nosetests', '-v'], cwd=os.environ['HOME'], env=env) # todo: remove
|
||||
shell(shlex.split(test_command_absolute), cwd=os.environ['HOME'], env=env)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Build wheels for all the platforms.',
|
||||
epilog=('Most options are supplied via environment variables. '
|
||||
'See https://github.com/joerick/cibuildwheel#options for info.'))
|
||||
|
||||
parser.add_argument('--platform',
|
||||
choices=['auto', 'linux', 'macos', 'windows'],
|
||||
default='auto',
|
||||
help=('Platform to build for. For "linux" you need docker running, on Mac '
|
||||
'or Linux. For "macos", you need a Mac machine, and note that this '
|
||||
'script is going to automatically install MacPython on your system, '
|
||||
'so don\'t run on your development machine. For "windows", you need to '
|
||||
'run in Windows, and it will build and test for all versions of '
|
||||
'Python at C:\\PythonXX[-x64]. Default: auto.'))
|
||||
parser.add_argument('--output-dir',
|
||||
default=os.environ.get('CIBW_OUTPUT_DIR', 'wheelhouse'),
|
||||
help='Destination folder for the wheels.')
|
||||
parser.add_argument('project_dir',
|
||||
default='.',
|
||||
nargs='?',
|
||||
help=('Path to the project that you want wheels for. Default: the current '
|
||||
'directory.'))
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
output_dir = args.output_dir
|
||||
test_command = os.environ.get('CIBW_TEST_COMMAND', None)
|
||||
test_requires = os.environ.get('CIBW_TEST_REQUIRES', '').split()
|
||||
project_dir = args.project_dir
|
||||
|
||||
try:
|
||||
project_setup_py = os.path.join(project_dir, 'setup.py')
|
||||
package_name = subprocess.check_output([sys.executable, project_setup_py, '--name'])
|
||||
except subprocess.CalledProcessError as err:
|
||||
if not os.path.exists(project_setup_py):
|
||||
print('cibuildwheel: Could not find setup.py at root of project', file=sys.stderr)
|
||||
exit(2)
|
||||
else:
|
||||
print('cibuildwheel: Failed to get name of the package. Command was %s' % err.cmd,
|
||||
file=sys.stderr)
|
||||
exit(2)
|
||||
|
||||
package_name = package_name.strip()
|
||||
|
||||
if package_name == '' or package_name == 'UNKNOWN':
|
||||
print('cibuildwheel: Invalid package name "%s". Check your setup.py' % package_name,
|
||||
file=sys.stderr)
|
||||
exit(2)
|
||||
|
||||
build_args = dict(
|
||||
project_dir=project_dir,
|
||||
package_name=package_name,
|
||||
output_dir=output_dir,
|
||||
test_command=test_command,
|
||||
test_requires=test_requires
|
||||
)
|
||||
|
||||
if args.platform != 'auto':
|
||||
platform = args.platform
|
||||
else:
|
||||
if os.environ.get('TRAVIS_OS_NAME') == 'linux':
|
||||
platform = 'linux'
|
||||
elif os.environ.get('TRAVIS_OS_NAME') == 'osx':
|
||||
platform = 'macos'
|
||||
elif 'APPVEYOR' in os.environ:
|
||||
platform = 'windows'
|
||||
else:
|
||||
print('Unable to detect platform. cibuildwheel should run on your CI server, '
|
||||
'Travis CI and Appveyor are supported. You can run on your development '
|
||||
'machine using the --platform argument. Check --help output for more '
|
||||
'information.',
|
||||
file=sys.stderr)
|
||||
exit(2)
|
||||
|
||||
if platform == 'linux':
|
||||
linux_build(**build_args)
|
||||
elif platform == 'windows':
|
||||
windows_build(**build_args)
|
||||
elif platform == 'macos':
|
||||
macos_build(**build_args)
|
||||
else:
|
||||
print('cibuildwheel: Unsupported platform %s' % platform, file=sys.stderr)
|
||||
exit(1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1 @@
|
||||
__version__ = '0.3.0'
|
||||
|
||||
+84
-38
@@ -1,7 +1,27 @@
|
||||
from __future__ import print_function
|
||||
import argparse, os, subprocess, sys
|
||||
import argparse, os, subprocess, sys, textwrap
|
||||
|
||||
import cibuildwheel
|
||||
import cibuildwheel.linux, cibuildwheel.windows, cibuildwheel.macos
|
||||
from cibuildwheel.util import BuildSkipper
|
||||
|
||||
def get_option_from_environment(option_name, platform=None):
|
||||
'''
|
||||
Returns an option from the environment, optionally scoped by the platform.
|
||||
|
||||
Example:
|
||||
get_option_from_environment('CIBW_COLOR', platform='macos')
|
||||
|
||||
This will return the value of CIBW_COLOR_MACOS if it exists, otherwise the value of
|
||||
CIBW_COLOR.
|
||||
'''
|
||||
if platform:
|
||||
option = os.environ.get('%s_%s' % (option_name, platform.upper()))
|
||||
if option is not None:
|
||||
return option
|
||||
|
||||
return os.environ.get(option_name)
|
||||
|
||||
from cibuildwheel import linux, windows, macos
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
@@ -11,7 +31,7 @@ def main():
|
||||
|
||||
parser.add_argument('--platform',
|
||||
choices=['auto', 'linux', 'macos', 'windows'],
|
||||
default='auto',
|
||||
default=os.environ.get('CIBW_PLATFORM', 'auto'),
|
||||
help=('Platform to build for. For "linux" you need docker running, on Mac '
|
||||
'or Linux. For "macos", you need a Mac machine, and note that this '
|
||||
'script is going to automatically install MacPython on your system, '
|
||||
@@ -29,38 +49,6 @@ def main():
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
output_dir = args.output_dir
|
||||
test_command = os.environ.get('CIBW_TEST_COMMAND', None)
|
||||
test_requires = os.environ.get('CIBW_TEST_REQUIRES', '').split()
|
||||
project_dir = args.project_dir
|
||||
|
||||
try:
|
||||
project_setup_py = os.path.join(project_dir, 'setup.py')
|
||||
package_name = subprocess.check_output([sys.executable, project_setup_py, '--name'])
|
||||
except subprocess.CalledProcessError as err:
|
||||
if not os.path.exists(project_setup_py):
|
||||
print('cibuildwheel: Could not find setup.py at root of project', file=sys.stderr)
|
||||
exit(2)
|
||||
else:
|
||||
print('cibuildwheel: Failed to get name of the package. Command was %s' % err.cmd,
|
||||
file=sys.stderr)
|
||||
exit(2)
|
||||
|
||||
package_name = package_name.strip()
|
||||
|
||||
if package_name == '' or package_name == 'UNKNOWN':
|
||||
print('cibuildwheel: Invalid package name "%s". Check your setup.py' % package_name,
|
||||
file=sys.stderr)
|
||||
exit(2)
|
||||
|
||||
build_args = dict(
|
||||
project_dir=project_dir,
|
||||
package_name=package_name,
|
||||
output_dir=output_dir,
|
||||
test_command=test_command,
|
||||
test_requires=test_requires
|
||||
)
|
||||
|
||||
if args.platform != 'auto':
|
||||
platform = args.platform
|
||||
else:
|
||||
@@ -78,14 +66,72 @@ def main():
|
||||
file=sys.stderr)
|
||||
exit(2)
|
||||
|
||||
output_dir = args.output_dir
|
||||
test_command = os.environ.get('CIBW_TEST_COMMAND', None)
|
||||
test_requires = os.environ.get('CIBW_TEST_REQUIRES', '').split()
|
||||
project_dir = args.project_dir
|
||||
before_build = get_option_from_environment('CIBW_BEFORE_BUILD', platform=platform)
|
||||
skip_config = os.environ.get('CIBW_SKIP', '')
|
||||
|
||||
skip = BuildSkipper(skip_config)
|
||||
|
||||
try:
|
||||
project_setup_py = os.path.join(project_dir, 'setup.py')
|
||||
name_output = subprocess.check_output([sys.executable, project_setup_py, '--name'])
|
||||
# the last line of output is the name
|
||||
package_name = name_output.strip().splitlines()[-1]
|
||||
except subprocess.CalledProcessError as err:
|
||||
if not os.path.exists(project_setup_py):
|
||||
print('cibuildwheel: Could not find setup.py at root of project', file=sys.stderr)
|
||||
exit(2)
|
||||
else:
|
||||
print('cibuildwheel: Failed to get name of the package. Command was %s' % err.cmd,
|
||||
file=sys.stderr)
|
||||
exit(2)
|
||||
|
||||
if package_name == '' or package_name == 'UNKNOWN':
|
||||
print('cibuildwheel: Invalid package name "%s". Check your setup.py' % package_name,
|
||||
file=sys.stderr)
|
||||
exit(2)
|
||||
|
||||
build_options = dict(
|
||||
project_dir=project_dir,
|
||||
package_name=package_name,
|
||||
output_dir=output_dir,
|
||||
test_command=test_command,
|
||||
test_requires=test_requires,
|
||||
before_build=before_build,
|
||||
skip=skip,
|
||||
)
|
||||
|
||||
print_preamble(platform, build_options)
|
||||
|
||||
if platform == 'linux':
|
||||
linux.build(**build_args)
|
||||
cibuildwheel.linux.build(**build_options)
|
||||
elif platform == 'windows':
|
||||
windows.build(**build_args)
|
||||
cibuildwheel.windows.build(**build_options)
|
||||
elif platform == 'macos':
|
||||
macos.build(**build_args)
|
||||
cibuildwheel.macos.build(**build_options)
|
||||
else:
|
||||
raise Exception('Unsupported platform')
|
||||
|
||||
def print_preamble(platform, build_options):
|
||||
print(textwrap.dedent('''
|
||||
_ _ _ _ _ _ _
|
||||
___|_| |_ _ _|_| |_| |_ _ _| |_ ___ ___| |
|
||||
| _| | . | | | | | . | | | | | -_| -_| |
|
||||
|___|_|___|___|_|_|___|_____|_|_|___|___|_|
|
||||
'''))
|
||||
|
||||
print('cibuildwheel version %s\n' % cibuildwheel.__version__)
|
||||
|
||||
|
||||
print('Build options:')
|
||||
print(' platform: %r' % platform)
|
||||
for option, value in build_options.items():
|
||||
print(' %s: %r' % (option, value))
|
||||
|
||||
print('\nHere we go!\n')
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
+61
-10
@@ -1,5 +1,7 @@
|
||||
from __future__ import print_function
|
||||
import os, subprocess
|
||||
from collections import namedtuple
|
||||
from .util import prepare_command
|
||||
|
||||
try:
|
||||
from shlex import quote as shlex_quote
|
||||
@@ -7,25 +9,66 @@ except ImportError:
|
||||
from pipes import quote as shlex_quote
|
||||
|
||||
|
||||
def build(project_dir, package_name, output_dir, test_command, test_requires):
|
||||
for docker_image in ['quay.io/pypa/manylinux1_x86_64', 'quay.io/pypa/manylinux1_i686']:
|
||||
def build(project_dir, package_name, output_dir, test_command, test_requires, before_build, skip):
|
||||
PythonConfiguration = namedtuple('PythonConfiguration', ['identifier', 'path'])
|
||||
python_configurations = [
|
||||
PythonConfiguration(identifier='cp27-manylinux1_x86_64', path='/opt/python/cp27-cp27m'),
|
||||
PythonConfiguration(identifier='cp27-manylinux1_x86_64', path='/opt/python/cp27-cp27mu'),
|
||||
PythonConfiguration(identifier='cp33-manylinux1_x86_64', path='/opt/python/cp33-cp33m'),
|
||||
PythonConfiguration(identifier='cp34-manylinux1_x86_64', path='/opt/python/cp34-cp34m'),
|
||||
PythonConfiguration(identifier='cp35-manylinux1_x86_64', path='/opt/python/cp35-cp35m'),
|
||||
PythonConfiguration(identifier='cp36-manylinux1_x86_64', path='/opt/python/cp36-cp36m'),
|
||||
PythonConfiguration(identifier='cp26-manylinux1_i686', path='/opt/python/cp26-cp26m'),
|
||||
PythonConfiguration(identifier='cp26-manylinux1_i686', path='/opt/python/cp26-cp26mu'),
|
||||
PythonConfiguration(identifier='cp27-manylinux1_i686', path='/opt/python/cp27-cp27m'),
|
||||
PythonConfiguration(identifier='cp27-manylinux1_i686', path='/opt/python/cp27-cp27mu'),
|
||||
PythonConfiguration(identifier='cp33-manylinux1_i686', path='/opt/python/cp33-cp33m'),
|
||||
PythonConfiguration(identifier='cp34-manylinux1_i686', path='/opt/python/cp34-cp34m'),
|
||||
PythonConfiguration(identifier='cp35-manylinux1_i686', path='/opt/python/cp35-cp35m'),
|
||||
PythonConfiguration(identifier='cp36-manylinux1_i686', path='/opt/python/cp36-cp36m'),
|
||||
]
|
||||
|
||||
# skip builds as required
|
||||
python_configurations = [c for c in python_configurations if not skip(c.identifier)]
|
||||
|
||||
platforms = [
|
||||
('manylinux1_x86_64', 'quay.io/pypa/manylinux1_x86_64'),
|
||||
('manylinux1_i686', 'quay.io/pypa/manylinux1_i686'),
|
||||
]
|
||||
|
||||
for platform_tag, docker_image in platforms:
|
||||
platform_configs = [c for c in python_configurations if c.identifier.endswith(platform_tag)]
|
||||
|
||||
bash_script = '''
|
||||
set -o errexit
|
||||
set -o xtrace
|
||||
cd /project
|
||||
|
||||
for PYBIN in /opt/python/*/bin; do
|
||||
"$PYBIN/pip" wheel . -w /tmp/linux_wheels
|
||||
for PYBIN in {pybin_paths}; do
|
||||
if [ ! -z {before_build} ]; then
|
||||
PATH=$PYBIN:$PATH sh -c {before_build}
|
||||
fi
|
||||
|
||||
# install the package first to take care of dependencies
|
||||
"$PYBIN/pip" install .
|
||||
|
||||
"$PYBIN/pip" wheel --no-deps . -w /tmp/linux_wheels
|
||||
done
|
||||
|
||||
for whl in /tmp/linux_wheels/*.whl; do
|
||||
auditwheel repair "$whl" -w /output
|
||||
if [[ "$whl" == *none-any.whl ]]; then
|
||||
# pure python wheel - just copy to the output
|
||||
cp "$whl" /output
|
||||
else
|
||||
auditwheel repair "$whl" -w /output
|
||||
fi
|
||||
done
|
||||
|
||||
# Install packages and test
|
||||
for PYBIN in /opt/python/*/bin/; do
|
||||
for PYBIN in {pybin_paths}; do
|
||||
# Install the wheel we just built
|
||||
"$PYBIN/pip" install {package_name} --no-index -f /output
|
||||
"$PYBIN/pip" install {package_name} \
|
||||
--upgrade --force-reinstall --no-deps --no-index -f /output
|
||||
|
||||
# Install any requirements to run the tests
|
||||
if [ ! -z "{test_requires}" ]; then
|
||||
@@ -34,13 +77,21 @@ def build(project_dir, package_name, output_dir, test_command, test_requires):
|
||||
|
||||
# Run the tests from a different directory
|
||||
if [ ! -z {test_command} ]; then
|
||||
(cd "$HOME" && export PATH=$PYBIN:$PATH && sh -c {test_command})
|
||||
pushd $HOME
|
||||
PATH=$PYBIN:$PATH sh -c {test_command}
|
||||
popd
|
||||
fi
|
||||
done
|
||||
'''.format(
|
||||
package_name=package_name,
|
||||
pybin_paths=' '.join(c.path+'/bin' for c in platform_configs),
|
||||
test_requires=' '.join(test_requires),
|
||||
test_command=shlex_quote(test_command.format(project='/project') if test_command else ''),
|
||||
test_command=shlex_quote(
|
||||
test_command.format(project='/project') if test_command else ''
|
||||
),
|
||||
before_build=shlex_quote(
|
||||
prepare_command(before_build, python='python', pip='pip') if before_build else ''
|
||||
),
|
||||
)
|
||||
|
||||
docker_process = subprocess.Popen([
|
||||
@@ -52,7 +103,7 @@ def build(project_dir, package_name, output_dir, test_command, test_requires):
|
||||
'-v', '%s:/output' % os.path.abspath(output_dir),
|
||||
docker_image,
|
||||
'/bin/bash'],
|
||||
stdin=subprocess.PIPE)
|
||||
stdin=subprocess.PIPE, universal_newlines=True)
|
||||
|
||||
docker_process.communicate(bash_script)
|
||||
|
||||
|
||||
+35
-21
@@ -1,5 +1,5 @@
|
||||
from __future__ import print_function
|
||||
import os, subprocess, shlex
|
||||
import os, subprocess, shlex, sys
|
||||
from collections import namedtuple
|
||||
from glob import glob
|
||||
try:
|
||||
@@ -7,14 +7,16 @@ try:
|
||||
except ImportError:
|
||||
from pipes import quote as shlex_quote
|
||||
|
||||
from .util import prepare_command
|
||||
|
||||
def build(project_dir, package_name, output_dir, test_command, test_requires):
|
||||
PythonConfiguration = namedtuple('PythonConfiguration', ['version', 'url'])
|
||||
|
||||
def build(project_dir, package_name, output_dir, test_command, test_requires, before_build, skip):
|
||||
PythonConfiguration = namedtuple('PythonConfiguration', ['version', 'identifier', 'url'])
|
||||
python_configurations = [
|
||||
PythonConfiguration(version='2.7', url='https://www.python.org/ftp/python/2.7.13/python-2.7.13-macosx10.6.pkg'),
|
||||
PythonConfiguration(version='3.4', url='https://www.python.org/ftp/python/3.4.4/python-3.4.4-macosx10.6.pkg'),
|
||||
PythonConfiguration(version='3.5', url='https://www.python.org/ftp/python/3.5.3/python-3.5.3-macosx10.6.pkg'),
|
||||
PythonConfiguration(version='3.6', url='https://www.python.org/ftp/python/3.6.0/python-3.6.0-macosx10.6.pkg'),
|
||||
PythonConfiguration(version='2.7', identifier='cp27-macosx_10_6_intel', url='https://www.python.org/ftp/python/2.7.13/python-2.7.13-macosx10.6.pkg'),
|
||||
PythonConfiguration(version='3.4', identifier='cp34-macosx_10_6_intel', url='https://www.python.org/ftp/python/3.4.4/python-3.4.4-macosx10.6.pkg'),
|
||||
PythonConfiguration(version='3.5', identifier='cp35-macosx_10_6_intel', url='https://www.python.org/ftp/python/3.5.3/python-3.5.3-macosx10.6.pkg'),
|
||||
PythonConfiguration(version='3.6', identifier='cp36-macosx_10_6_intel', url='https://www.python.org/ftp/python/3.6.0/python-3.6.0-macosx10.6.pkg'),
|
||||
]
|
||||
|
||||
def shell(args, env=None, cwd=None):
|
||||
@@ -23,6 +25,10 @@ def build(project_dir, package_name, output_dir, test_command, test_requires):
|
||||
return subprocess.check_call(args, env=env, cwd=cwd)
|
||||
|
||||
for config in python_configurations:
|
||||
if skip(config.identifier):
|
||||
print('cibuildwheel: Skipping build %s' % config.identifier, file=sys.stderr)
|
||||
continue
|
||||
|
||||
# download the pkg
|
||||
shell(['curl', '-L', '-o', '/tmp/Python.pkg', config.url])
|
||||
# install
|
||||
@@ -43,35 +49,43 @@ def build(project_dir, package_name, output_dir, test_command, test_requires):
|
||||
|
||||
# install pip & wheel
|
||||
shell([python, '-m', 'ensurepip', '--upgrade'], env=env)
|
||||
shell(['which', pip], env=env) # todo: remove
|
||||
shell([pip, '--version'])
|
||||
shell([pip, '--version'], env=env)
|
||||
shell([pip, 'install', 'wheel'], env=env)
|
||||
shell([pip, 'install', 'delocate'], env=env)
|
||||
|
||||
# run the before_build command
|
||||
if before_build:
|
||||
before_build_prepared = prepare_command(before_build, python=python, pip=pip)
|
||||
shell(shlex.split(before_build_prepared), env=env)
|
||||
|
||||
# install the package first to take care of dependencies
|
||||
shell([pip, 'install', project_dir], env=env)
|
||||
|
||||
# build the wheel to temp dir
|
||||
temp_wheel_dir = '/tmp/tmpwheel%s' % config.version
|
||||
shell([pip, 'wheel', project_dir, '-w', temp_wheel_dir], env=env)
|
||||
shell([pip, 'wheel', project_dir, '-w', temp_wheel_dir, '--no-deps'], env=env)
|
||||
temp_wheel = glob(temp_wheel_dir+'/*.whl')[0]
|
||||
|
||||
# list the dependencies
|
||||
shell(['delocate-listdeps', temp_wheel], env=env)
|
||||
# rebuild the wheel with shared libraries included and place in output dir
|
||||
shell(['delocate-wheel', '-w', output_dir, temp_wheel], env=env)
|
||||
if temp_wheel.endswith('none-any.whl'):
|
||||
# pure python wheel - just copy to output_dir
|
||||
shell(['cp', temp_wheel, output_dir], env=env)
|
||||
else:
|
||||
# list the dependencies
|
||||
shell(['delocate-listdeps', temp_wheel], env=env)
|
||||
# rebuild the wheel with shared libraries included and place in output dir
|
||||
shell(['delocate-wheel', '-w', output_dir, temp_wheel], env=env)
|
||||
|
||||
# install the wheel
|
||||
shell([pip, 'install', package_name, '--no-index', '--find-links', output_dir], env=env)
|
||||
# now install the package from the generated wheel
|
||||
shell([pip, 'install', package_name, '--upgrade', '--force-reinstall',
|
||||
'--no-deps', '--no-index', '--find-links', output_dir], env=env)
|
||||
|
||||
# test the wheel
|
||||
if test_requires:
|
||||
shell([pip, '-v', 'install'] + test_requires, env=env)
|
||||
shell([pip, 'install'] + test_requires, env=env)
|
||||
if test_command:
|
||||
# run the tests from $HOME, with an absolute path in the command
|
||||
# (this ensures that Python runs the tests against the installed wheel
|
||||
# and not the repo code)
|
||||
abs_project_dir = os.path.abspath(project_dir)
|
||||
test_command_absolute = test_command.format(project=abs_project_dir)
|
||||
shell(['which', 'nosetests'], env=env) # todo: remove
|
||||
shell(['which', 'nosetests'], cwd=os.environ['HOME'], env=env) # todo: remove
|
||||
shell(['nosetests', '-v'], env=env) # todo: remove
|
||||
shell(['nosetests', '-v'], cwd=os.environ['HOME'], env=env) # todo: remove
|
||||
shell(shlex.split(test_command_absolute), cwd=os.environ['HOME'], env=env)
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
from fnmatch import fnmatch
|
||||
|
||||
|
||||
def prepare_command(command, python, pip):
|
||||
'''
|
||||
Preprocesses a command by expanding variables like {python} or {pip}.
|
||||
|
||||
For example, used for the before_build option, where the user would
|
||||
like to run a command like `python setup.py test`. If the command should run on
|
||||
Python 3, the user could write `{python} setup.py test`. This command would expand
|
||||
it out to python2 or python3 as appropriate.
|
||||
'''
|
||||
return command.format(python=python, pip=pip)
|
||||
|
||||
|
||||
class BuildSkipper(object):
|
||||
def __init__(self, skip_config):
|
||||
self.patterns = skip_config.split()
|
||||
|
||||
def __call__(self, build_id):
|
||||
return any(fnmatch(build_id, pattern) for pattern in self.patterns)
|
||||
|
||||
def __repr__(self):
|
||||
return 'BuildSkipper(%r)' % ' '.join(self.patterns)
|
||||
+36
-16
@@ -1,14 +1,20 @@
|
||||
from __future__ import print_function
|
||||
import os, tempfile, subprocess, urllib2
|
||||
import os, tempfile, subprocess, sys
|
||||
try:
|
||||
from urllib2 import urlopen
|
||||
except ImportError:
|
||||
from urllib.request import urlopen
|
||||
from collections import namedtuple
|
||||
|
||||
from .util import prepare_command
|
||||
|
||||
def build(project_dir, package_name, output_dir, test_command, test_requires):
|
||||
|
||||
def build(project_dir, package_name, output_dir, test_command, test_requires, before_build, skip):
|
||||
# run_with_env is a cmd file that sets the right environment variables to
|
||||
run_with_env = os.path.join(tempfile.gettempdir(), 'appveyor_run_with_env.cmd')
|
||||
if not os.path.exists(run_with_env):
|
||||
with open(run_with_env, 'wb') as f:
|
||||
request = urllib2.urlopen('https://github.com/ogrisel/python-appveyor-demo/raw/09a1c8672e5015a74d8f69d07add6ee803c176ec/appveyor/run_with_env.cmd')
|
||||
request = urlopen('https://github.com/ogrisel/python-appveyor-demo/raw/09a1c8672e5015a74d8f69d07add6ee803c176ec/appveyor/run_with_env.cmd')
|
||||
f.write(request.read())
|
||||
|
||||
def shell(args, env=None, cwd=None):
|
||||
@@ -17,21 +23,25 @@ def build(project_dir, package_name, output_dir, test_command, test_requires):
|
||||
args = ['cmd', '/E:ON', '/V:ON', '/C', run_with_env] + args
|
||||
return subprocess.check_call(' '.join(args), env=env, cwd=cwd)
|
||||
|
||||
PythonConfiguration = namedtuple('PythonConfiguration', ['version', 'arch', 'path'])
|
||||
PythonConfiguration = namedtuple('PythonConfiguration', ['version', 'arch', 'identifier', 'path'])
|
||||
python_configurations = [
|
||||
PythonConfiguration(version='2.7.x', arch="32", path='C:\Python27'),
|
||||
PythonConfiguration(version='2.7.x', arch="64", path='C:\Python27-x64'),
|
||||
PythonConfiguration(version='3.3.x', arch="32", path='C:\Python33'),
|
||||
PythonConfiguration(version='3.3.x', arch="64", path='C:\Python33-x64'),
|
||||
PythonConfiguration(version='3.4.x', arch="32", path='C:\Python34'),
|
||||
PythonConfiguration(version='3.4.x', arch="64", path='C:\Python34-x64'),
|
||||
PythonConfiguration(version='3.5.x', arch="32", path='C:\Python35'),
|
||||
PythonConfiguration(version='3.5.x', arch="64", path='C:\Python35-x64'),
|
||||
PythonConfiguration(version='3.6.x', arch="32", path='C:\Python36'),
|
||||
PythonConfiguration(version='3.6.x', arch="64", path='C:\Python36-x64'),
|
||||
PythonConfiguration(version='2.7.x', arch="32", identifier='cp27-win32', path='C:\Python27'),
|
||||
PythonConfiguration(version='2.7.x', arch="64", identifier='cp27-win_amd64', path='C:\Python27-x64'),
|
||||
PythonConfiguration(version='3.3.x', arch="32", identifier='cp33-win32', path='C:\Python33'),
|
||||
PythonConfiguration(version='3.3.x', arch="64", identifier='cp33-win_amd64', path='C:\Python33-x64'),
|
||||
PythonConfiguration(version='3.4.x', arch="32", identifier='cp34-win32', path='C:\Python34'),
|
||||
PythonConfiguration(version='3.4.x', arch="64", identifier='cp34-win_amd64', path='C:\Python34-x64'),
|
||||
PythonConfiguration(version='3.5.x', arch="32", identifier='cp35-win32', path='C:\Python35'),
|
||||
PythonConfiguration(version='3.5.x', arch="64", identifier='cp35-win_amd64', path='C:\Python35-x64'),
|
||||
PythonConfiguration(version='3.6.x', arch="32", identifier='cp36-win32', path='C:\Python36'),
|
||||
PythonConfiguration(version='3.6.x', arch="64", identifier='cp36-win_amd64', path='C:\Python36-x64'),
|
||||
]
|
||||
|
||||
for config in python_configurations:
|
||||
if skip(config.identifier):
|
||||
print('cibuildwheel: Skipping build %s' % config.identifier, file=sys.stderr)
|
||||
continue
|
||||
|
||||
env = os.environ.copy()
|
||||
# set up environment variables for run_with_env
|
||||
env['PYTHON_VERSION'] = config.version
|
||||
@@ -51,11 +61,21 @@ def build(project_dir, package_name, output_dir, test_command, test_requires):
|
||||
env=env)
|
||||
shell(['pip', 'install', 'wheel'], env=env)
|
||||
|
||||
# run the before_build command
|
||||
if before_build:
|
||||
before_build_prepared = prepare_command(before_build, python='python', pip='pip')
|
||||
shell([before_build_prepared], env=env)
|
||||
|
||||
# install the package first to take care of dependencies
|
||||
shell(['pip', 'install', project_dir], env=env)
|
||||
|
||||
# build the wheel
|
||||
shell(['pip', 'wheel', project_dir, '-w', output_dir], env=env)
|
||||
shell(['pip', 'wheel', project_dir, '-w', output_dir, '--no-deps'], env=env)
|
||||
|
||||
# install the wheel
|
||||
shell(['pip', 'install', package_name, '--no-index', '-f', output_dir], env=env)
|
||||
shell(['pip', 'install', package_name, '--upgrade',
|
||||
'--force-reinstall', '--no-deps', '--no-index', '-f',
|
||||
output_dir], env=env)
|
||||
|
||||
# test the wheel
|
||||
if test_requires:
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
from __future__ import print_function
|
||||
import os, sys, subprocess, shutil, json
|
||||
from glob import glob
|
||||
|
||||
# move cwd to the project root
|
||||
os.chdir(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
test_projects = glob('test/??_*')
|
||||
|
||||
if len(test_projects) == 0:
|
||||
print('No test projects found. Aborting.', file=sys.stderr)
|
||||
exit(2)
|
||||
|
||||
print('Testing projects:', test_projects)
|
||||
|
||||
for project_path in test_projects:
|
||||
# load project settings into environment
|
||||
env_file = os.path.join(project_path, 'environment.json')
|
||||
project_env = {}
|
||||
if os.path.exists(env_file):
|
||||
with open(env_file) as f:
|
||||
project_env = json.load(f)
|
||||
|
||||
# run the build
|
||||
env = os.environ.copy()
|
||||
project_env = {str(k): str(v) for k, v in project_env.items()} # unicode not allowed in env
|
||||
env.update(project_env)
|
||||
print('Building %s with environment %s' % (project_path, project_env))
|
||||
subprocess.check_call(['cibuildwheel', project_path], env=env)
|
||||
wheels = glob('wheelhouse/*.whl')
|
||||
print('%s built successfully. %i wheels built.' % (project_path, len(wheels)))
|
||||
|
||||
# check some wheels were actually built
|
||||
assert len(wheels) >= 4
|
||||
|
||||
# clean up
|
||||
shutil.rmtree('wheelhouse')
|
||||
|
||||
print('%d projects built successfully.' % len(test_projects))
|
||||
@@ -1,8 +1,14 @@
|
||||
[bumpversion]
|
||||
current_version = 0.1.1
|
||||
current_version = 0.3.0
|
||||
commit = True
|
||||
tag = True
|
||||
message = Bump version
|
||||
|
||||
[bumpversion:file:setup.py]
|
||||
|
||||
[bumpversion:file:cibuildwheel/__init__.py]
|
||||
|
||||
[bumpversion:file:README.md]
|
||||
search = cibuildwheel=={current_version}
|
||||
replace = cibuildwheel=={new_version}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ except ImportError:
|
||||
|
||||
setup(
|
||||
name='cibuildwheel',
|
||||
version='0.1.1',
|
||||
version='0.3.0',
|
||||
description="Build Python wheels on CI with minimal configuration.",
|
||||
long_description='For readme please see http://github.com/joerick/cibuildwheel',
|
||||
author="Joe Rickerby",
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
from setuptools import setup, Extension
|
||||
|
||||
setup(
|
||||
name="spam",
|
||||
ext_modules=[Extension('spam', sources=['spam.c'])],
|
||||
version="0.1.0",
|
||||
)
|
||||
@@ -0,0 +1,48 @@
|
||||
#include <Python.h>
|
||||
|
||||
static PyObject *
|
||||
spam_system(PyObject *self, PyObject *args)
|
||||
{
|
||||
const char *command;
|
||||
int sts;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "s", &command))
|
||||
return NULL;
|
||||
sts = system(command);
|
||||
return PyLong_FromLong(sts);
|
||||
}
|
||||
|
||||
/* Module initialization */
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
#define MOD_INIT(name) PyMODINIT_FUNC PyInit_##name(void)
|
||||
#define MOD_DEF(m, name, doc, methods, module_state_size) \
|
||||
static struct PyModuleDef moduledef = { \
|
||||
PyModuleDef_HEAD_INIT, name, doc, module_state_size, methods, }; \
|
||||
m = PyModule_Create(&moduledef);
|
||||
#define MOD_RETURN(m) return m;
|
||||
#else
|
||||
#define MOD_INIT(name) PyMODINIT_FUNC init##name(void)
|
||||
#define MOD_DEF(m, name, doc, methods, module_state_size) \
|
||||
m = Py_InitModule3(name, methods, doc);
|
||||
#define MOD_RETURN(m) return;
|
||||
#endif
|
||||
|
||||
static PyMethodDef module_methods[] = {
|
||||
{"system", (PyCFunction)spam_system, METH_VARARGS,
|
||||
"Execute a shell command."},
|
||||
{NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
MOD_INIT(spam)
|
||||
{
|
||||
PyObject* m;
|
||||
|
||||
MOD_DEF(m,
|
||||
"spam",
|
||||
"Example module",
|
||||
module_methods,
|
||||
-1)
|
||||
|
||||
MOD_RETURN(m)
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"CIBW_TEST_REQUIRES": "nose",
|
||||
"CIBW_TEST_COMMAND": "nosetests {project}/test"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
from setuptools import setup, Extension
|
||||
|
||||
setup(
|
||||
name="spam",
|
||||
ext_modules=[Extension('spam', sources=['spam.c'])],
|
||||
version="0.1.0",
|
||||
)
|
||||
@@ -0,0 +1,48 @@
|
||||
#include <Python.h>
|
||||
|
||||
static PyObject *
|
||||
spam_system(PyObject *self, PyObject *args)
|
||||
{
|
||||
const char *command;
|
||||
int sts;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "s", &command))
|
||||
return NULL;
|
||||
sts = system(command);
|
||||
return PyLong_FromLong(sts);
|
||||
}
|
||||
|
||||
/* Module initialization */
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
#define MOD_INIT(name) PyMODINIT_FUNC PyInit_##name(void)
|
||||
#define MOD_DEF(m, name, doc, methods, module_state_size) \
|
||||
static struct PyModuleDef moduledef = { \
|
||||
PyModuleDef_HEAD_INIT, name, doc, module_state_size, methods, }; \
|
||||
m = PyModule_Create(&moduledef);
|
||||
#define MOD_RETURN(m) return m;
|
||||
#else
|
||||
#define MOD_INIT(name) PyMODINIT_FUNC init##name(void)
|
||||
#define MOD_DEF(m, name, doc, methods, module_state_size) \
|
||||
m = Py_InitModule3(name, methods, doc);
|
||||
#define MOD_RETURN(m) return;
|
||||
#endif
|
||||
|
||||
static PyMethodDef module_methods[] = {
|
||||
{"system", (PyCFunction)spam_system, METH_VARARGS,
|
||||
"Execute a shell command."},
|
||||
{NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
MOD_INIT(spam)
|
||||
{
|
||||
PyObject* m;
|
||||
|
||||
MOD_DEF(m,
|
||||
"spam",
|
||||
"Example module",
|
||||
module_methods,
|
||||
-1)
|
||||
|
||||
MOD_RETURN(m)
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
from unittest import TestCase
|
||||
import spam
|
||||
|
||||
|
||||
class TestSpam(TestCase):
|
||||
def test_system(self):
|
||||
self.assertEqual(0, spam.system('python -c "exit(0)"'))
|
||||
self.assertNotEqual(0, spam.system('python -c "exit(1)"'))
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"CIBW_BEFORE_BUILD": "{python} -c \"import sys; open('/tmp/pythonversion.txt', 'w').write(sys.version)\"",
|
||||
"CIBW_BEFORE_BUILD_WINDOWS": "{python} -c \"import sys; open('c:\\pythonversion.txt', 'w').write(sys.version)\""
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
from setuptools import setup, Extension
|
||||
import sys
|
||||
|
||||
if sys.argv[-1] != '--name':
|
||||
# assert that the Python version as written to version.txt in the CIBW_BEFORE_BUILD step
|
||||
# is the same one as is currently running.
|
||||
version_file = 'c:\\pythonversion.txt' if sys.platform == 'win32' else '/tmp/pythonversion.txt'
|
||||
with open(version_file) as f:
|
||||
stored_version = f.read()
|
||||
print('stored_version', stored_version)
|
||||
print('sys.version', sys.version)
|
||||
assert stored_version == sys.version
|
||||
|
||||
setup(
|
||||
name="spam",
|
||||
ext_modules=[Extension('spam', sources=['spam.c'])],
|
||||
version="0.1.0",
|
||||
)
|
||||
@@ -0,0 +1,48 @@
|
||||
#include <Python.h>
|
||||
|
||||
static PyObject *
|
||||
spam_system(PyObject *self, PyObject *args)
|
||||
{
|
||||
const char *command;
|
||||
int sts;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "s", &command))
|
||||
return NULL;
|
||||
sts = system(command);
|
||||
return PyLong_FromLong(sts);
|
||||
}
|
||||
|
||||
/* Module initialization */
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
#define MOD_INIT(name) PyMODINIT_FUNC PyInit_##name(void)
|
||||
#define MOD_DEF(m, name, doc, methods, module_state_size) \
|
||||
static struct PyModuleDef moduledef = { \
|
||||
PyModuleDef_HEAD_INIT, name, doc, module_state_size, methods, }; \
|
||||
m = PyModule_Create(&moduledef);
|
||||
#define MOD_RETURN(m) return m;
|
||||
#else
|
||||
#define MOD_INIT(name) PyMODINIT_FUNC init##name(void)
|
||||
#define MOD_DEF(m, name, doc, methods, module_state_size) \
|
||||
m = Py_InitModule3(name, methods, doc);
|
||||
#define MOD_RETURN(m) return;
|
||||
#endif
|
||||
|
||||
static PyMethodDef module_methods[] = {
|
||||
{"system", (PyCFunction)spam_system, METH_VARARGS,
|
||||
"Execute a shell command."},
|
||||
{NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
MOD_INIT(spam)
|
||||
{
|
||||
PyObject* m;
|
||||
|
||||
MOD_DEF(m,
|
||||
"spam",
|
||||
"Example module",
|
||||
module_methods,
|
||||
-1)
|
||||
|
||||
MOD_RETURN(m)
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
3.6.0 (default, Feb 7 2017, 23:55:32)
|
||||
[GCC 4.8.2 20140120 (Red Hat 4.8.2-15)]
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"CIBW_SKIP": "cp26-* cp33-*"
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
from setuptools import setup, Extension
|
||||
import sys
|
||||
|
||||
if sys.argv[-1] != '--name':
|
||||
# explode if run on Python 2.6 or Python 3.3 (these should be skipped)
|
||||
if sys.version_info[0:2] == (2, 6):
|
||||
raise Exception('Python 2.6 should be skipped')
|
||||
if sys.version_info[0:2] == (3, 3):
|
||||
raise Exception('Python 3.3 should be skipped')
|
||||
|
||||
setup(
|
||||
name="spam",
|
||||
ext_modules=[Extension('spam', sources=['spam.c'])],
|
||||
version="0.1.0",
|
||||
)
|
||||
@@ -0,0 +1,48 @@
|
||||
#include <Python.h>
|
||||
|
||||
static PyObject *
|
||||
spam_system(PyObject *self, PyObject *args)
|
||||
{
|
||||
const char *command;
|
||||
int sts;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "s", &command))
|
||||
return NULL;
|
||||
sts = system(command);
|
||||
return PyLong_FromLong(sts);
|
||||
}
|
||||
|
||||
/* Module initialization */
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
#define MOD_INIT(name) PyMODINIT_FUNC PyInit_##name(void)
|
||||
#define MOD_DEF(m, name, doc, methods, module_state_size) \
|
||||
static struct PyModuleDef moduledef = { \
|
||||
PyModuleDef_HEAD_INIT, name, doc, module_state_size, methods, }; \
|
||||
m = PyModule_Create(&moduledef);
|
||||
#define MOD_RETURN(m) return m;
|
||||
#else
|
||||
#define MOD_INIT(name) PyMODINIT_FUNC init##name(void)
|
||||
#define MOD_DEF(m, name, doc, methods, module_state_size) \
|
||||
m = Py_InitModule3(name, methods, doc);
|
||||
#define MOD_RETURN(m) return;
|
||||
#endif
|
||||
|
||||
static PyMethodDef module_methods[] = {
|
||||
{"system", (PyCFunction)spam_system, METH_VARARGS,
|
||||
"Execute a shell command."},
|
||||
{NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
MOD_INIT(spam)
|
||||
{
|
||||
PyObject* m;
|
||||
|
||||
MOD_DEF(m,
|
||||
"spam",
|
||||
"Example module",
|
||||
module_methods,
|
||||
-1)
|
||||
|
||||
MOD_RETURN(m)
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
3.6.0 (default, Feb 7 2017, 23:55:32)
|
||||
[GCC 4.8.2 20140120 (Red Hat 4.8.2-15)]
|
||||
@@ -0,0 +1,3 @@
|
||||
This folder contains repos that are built by cibuildwheel to test cibuildwheel. Confusing, I know.
|
||||
|
||||
Basically, if cibuildwheel can build these projects, we're good.
|
||||
Reference in New Issue
Block a user