diff --git a/.travis.yml b/.travis.yml index a4cb2b794..b876bef72 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,11 +27,11 @@ matrix: - os: windows language: shell before_install: - - choco install python3 --version 3.6.8 --no-progress -y + - choco install python3 --version 3.7.5 --no-progress -y install: - - C:\\Python36\\python -m pip install -r requirements-dev.txt + - C:\\Python37\\python -m pip install -r requirements-dev.txt script: - - C:\\Python36\\python ./bin/run_tests.py + - C:\\Python37\\python ./bin/run_tests.py install: $PYTHON -m pip install -r requirements-dev.txt diff --git a/README.md b/README.md index 6cbf21b3a..0b0d5ce37 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,9 @@ What does it do? | Python 3.5 | ✅ | ✅ | ✅ | ✅ | ✅ | | Python 3.6 | ✅ | ✅ | ✅ | ✅ | ✅ | | Python 3.7 | ✅ | ✅ | ✅ | ✅ | ✅ | +| Python 3.8 | ✅ | ✅ | ✅ | ✅ | ✅ | -> ¹ Not supported on Azure Pipelines +> ¹ Not supported on Azure Pipelines > > ² Not supported on Travis @@ -58,7 +59,7 @@ Usage jobs: - job: linux pool: {vmImage: 'Ubuntu-16.04'} - steps: + steps: - task: UsePythonVersion@0 - bash: | python -m pip install --upgrade pip @@ -68,7 +69,7 @@ jobs: inputs: {pathtoPublish: 'wheelhouse'} - job: macos pool: {vmImage: 'macOS-10.13'} - steps: + steps: - task: UsePythonVersion@0 - bash: | python -m pip install --upgrade pip @@ -78,15 +79,9 @@ jobs: inputs: {pathtoPublish: 'wheelhouse'} - job: windows pool: {vmImage: 'vs2017-win2016'} - steps: + steps: - {task: UsePythonVersion@0, inputs: {versionSpec: '2.7', architecture: x86}} - {task: UsePythonVersion@0, inputs: {versionSpec: '2.7', architecture: x64}} - - {task: UsePythonVersion@0, inputs: {versionSpec: '3.5', architecture: x86}} - - {task: UsePythonVersion@0, inputs: {versionSpec: '3.5', architecture: x64}} - - {task: UsePythonVersion@0, inputs: {versionSpec: '3.6', architecture: x86}} - - {task: UsePythonVersion@0, inputs: {versionSpec: '3.6', architecture: x64}} - - {task: UsePythonVersion@0, inputs: {versionSpec: '3.7', architecture: x86}} - - {task: UsePythonVersion@0, inputs: {versionSpec: '3.7', architecture: x64}} - script: choco install vcpython27 -f -y displayName: Install Visual C++ for Python 2.7 - bash: | @@ -110,7 +105,7 @@ jobs: ```yaml language: python - + matrix: include: - sudo: required @@ -126,7 +121,7 @@ jobs: - cibuildwheel --output-dir wheelhouse ``` - To build on Windows too, add this matrix entry: + To build on Windows too, add this matrix entry: ```yaml - os: windows language: shell @@ -147,7 +142,7 @@ jobs: - + - To build Linux and Mac wheels on CircleCI, create a `.circleci/config.yml` file in your repo, ``` @@ -213,14 +208,14 @@ jobs: - path: "wheelhouse\\*.whl" name: Wheels ``` - + AppVeyor will store the built wheels for you - you can access them from the project console. Alternatively, you may want to store them in the same place as the Travis CI build. See [AppVeyor deployment docs](https://www.appveyor.com/docs/deployment/) for more info, or see [Delivering to PyPI](#delivering-to-pypi) below. - + - Commit those files, enable building of your repo on Travis CI and AppVeyor, and push. -All being well, you should get wheels delivered to you in a few minutes. +All being well, you should get wheels delivered to you in a few minutes. > ⚠️ Got an error? Check the [checklist](#it-didnt-work) below. @@ -259,7 +254,7 @@ Options usage: cibuildwheel [-h] [--platform {auto,linux,macos,windows}] [--output-dir OUTPUT_DIR] [--print-build-identifiers] [project_dir] - + Build wheels for all the platforms. positional arguments: @@ -312,7 +307,7 @@ When both options are specified, both conditions are applied and only builds wit The format is `python_tag-platform_tag`. The tags are similar but not identical to the ones defined in [PEP 425](https://www.python.org/dev/peps/pep-0425/#details). -Python tags look like `cp27` `cp34` `cp35` `cp36` `cp37` +Python tags look like `cp27` `cp34` `cp35` `cp36` `cp37` `cp38` Platform tags look like `macosx_10_6_intel` `manylinux_x86_64` `manylinux_i686` `win32` `win_amd64` @@ -355,10 +350,10 @@ You must set this variable to pass variables to Linux builds (since they execute You can use `$PATH` syntax to insert other variables, or the `$(pwd)` syntax to insert the output of other shell commands. -Example: `CFLAGS="-g -Wall" CXXFLAGS="-Wall"` -Example: `PATH=$PATH:/usr/local/bin` -Example: `BUILD_TIME="$(date)"` -Example: `PIP_EXTRA_INDEX_URL="https://pypi.myorg.com/simple"` +Example: `CFLAGS="-g -Wall" CXXFLAGS="-Wall"`\ +Example: `PATH=$PATH:/usr/local/bin`\ +Example: `BUILD_TIME="$(date)"`\ +Example: `PIP_EXTRA_INDEX_URL="https://pypi.myorg.com/simple"`\ Platform-specific variants also available: `CIBW_ENVIRONMENT_MACOS` | `CIBW_ENVIRONMENT_WINDOWS` | `CIBW_ENVIRONMENT_LINUX` @@ -378,11 +373,11 @@ If dependencies are required to build your wheel (for example if you include a h The active Python binary can be accessed using `python`, and pip with `pip`; `cibuildwheel` makes sure the right version of Python and pip will be executed. `{project}` can be used as a placeholder for the absolute path to the project's root. -Example: `pip install .` -Example: `pip install pybind11` +Example: `pip install .`\ +Example: `pip install pybind11`\ Example: `yum install -y libffi-dev && pip install .` -Platform-specific variants also available: +Platform-specific variants also available:\ `CIBW_BEFORE_BUILD_MACOS` | `CIBW_BEFORE_BUILD_WINDOWS` | `CIBW_BEFORE_BUILD_LINUX` *** @@ -400,8 +395,8 @@ Beware to specify a valid Docker image that can be used in the same way as the o Note that `auditwheel` detects the version of the `manylinux` standard in the Docker image through the `AUDITWHEEL_PLAT` environment variable, as `cibuildwheel` has no way of detecting the correct `--plat` command line argument to pass to `auditwheel` for a custom image. If a Docker image does not correctly set this `AUDITWHEEL_PLAT` environment variable, the `CIBW_ENVIRONMENT` option can be used to do so (e.g., `CIBW_ENVIRONMENT="manylinux2010_$(uname -m)"`). -Example: `manylinux1` -Example: `dockcross/manylinux-x64` +Example: `manylinux1`\ +Example: `dockcross/manylinux-x64`\ Example: `dockcross/manylinux-x86` *** @@ -413,7 +408,7 @@ Optional. Shell command to run tests after the build. The wheel will be installed automatically and available for import from the tests. `{project}` can be used as a placeholder for the absolute path to the project's root and will be replaced by `cibuildwheel`. -On Linux and Mac, the command runs in a shell, so you can write things like `cmd1 && cmd2`. +On Linux and Mac, the command runs in a shell, so you can write things like `cmd1 && cmd2`. Example: `nosetests {project}/tests` @@ -429,7 +424,7 @@ Optional. Space-separated list of dependencies required for running the tests. -Example: `pytest` +Example: `pytest`\ Example: `nose==1.3.7 moto==0.4.31` Platform-specific variants also available: @@ -480,13 +475,13 @@ After you've built your wheels, you'll probably want to deliver them to PyPI. On your development machine, do the following... ```bash -# Clear out your 'dist' folder. +# 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 +# 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') @@ -518,7 +513,7 @@ If your wheel didn't compile, check the list below for some debugging tips. Working examples ---------------- -Here are some repos that use cibuildwheel. +Here are some repos that use cibuildwheel. - [pyinstrument_cext](https://github.com/joerick/pyinstrument_cext) - [websockets](https://github.com/aaugustin/websockets) @@ -536,7 +531,7 @@ Here are some repos that use cibuildwheel. Legal note ---------- -Since `cibuildwheel` runs the wheel through delocate or auditwheel, it might automatically bundle dynamically linked libraries from the build machine. +Since `cibuildwheel` runs the wheel through delocate or auditwheel, it might automatically bundle dynamically linked libraries from the build machine. It helps ensure that the library can run without any dependencies outside of the pip toolchain. @@ -568,7 +563,7 @@ _26 May 2019_ - ✨ 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 + easiest to set up! Check out the quickstart in the docs, or [cibuildwheel-azure-example](https://github.com/joerick/cibuildwheel-azure-example) for an example project. (#126, #132) - 🛠 Internal change - the end-to-end test projects format was updated, so we @@ -708,7 +703,7 @@ _11 June 2017_ _13 April 2017_ -- ✨ Added `CIBW_SKIP` option, letting users explicitly skip a build +- ✨ 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 @@ -750,7 +745,7 @@ Maintainers Credits ------- -`cibuildwheel` stands on the shoulders of giants. +`cibuildwheel` stands on the shoulders of giants. - ⭐️ @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) diff --git a/appveyor.yml b/appveyor.yml index a8463c2f9..db8f6f4e8 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -5,6 +5,6 @@ environment: build_script: - "%PYTHON% -m pip install -r requirements-dev.txt" - # the '-u' flag is required so the output is in the correct order. + # the '-u' flag is required so the output is in the correct order. # See https://github.com/joerick/cibuildwheel/pull/24 for more info. - "%PYTHON% -u ./bin/run_tests.py" diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 5a9e501ce..044de1c5e 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,7 +1,7 @@ jobs: - job: linux pool: {vmImage: 'Ubuntu-16.04'} - steps: + steps: - task: UsePythonVersion@0 - bash: | python -m pip install -r requirements-dev.txt @@ -9,7 +9,7 @@ jobs: - job: macos pool: {vmImage: 'macOS-10.13'} - steps: + steps: - task: UsePythonVersion@0 - bash: | python -m pip install -r requirements-dev.txt @@ -17,15 +17,9 @@ jobs: - job: windows pool: {vmImage: 'vs2017-win2016'} - steps: + steps: - {task: UsePythonVersion@0, inputs: {versionSpec: '2.7', architecture: x86}} - {task: UsePythonVersion@0, inputs: {versionSpec: '2.7', architecture: x64}} - - {task: UsePythonVersion@0, inputs: {versionSpec: '3.5', architecture: x86}} - - {task: UsePythonVersion@0, inputs: {versionSpec: '3.5', architecture: x64}} - - {task: UsePythonVersion@0, inputs: {versionSpec: '3.6', architecture: x86}} - - {task: UsePythonVersion@0, inputs: {versionSpec: '3.6', architecture: x64}} - - {task: UsePythonVersion@0, inputs: {versionSpec: '3.7', architecture: x86}} - - {task: UsePythonVersion@0, inputs: {versionSpec: '3.7', architecture: x64}} - script: choco install vcpython27 -f -y displayName: Install Visual C++ for Python 2.7 - bash: | diff --git a/cibuildwheel/_vendored/__init__.py b/cibuildwheel/_vendored/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/cibuildwheel/_vendored/pep514tools/LICENSE b/cibuildwheel/_vendored/pep514tools/LICENSE new file mode 100755 index 000000000..c7ac395fb --- /dev/null +++ b/cibuildwheel/_vendored/pep514tools/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Steve Dower + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/cibuildwheel/_vendored/pep514tools/__init__.py b/cibuildwheel/_vendored/pep514tools/__init__.py new file mode 100755 index 000000000..52568f85c --- /dev/null +++ b/cibuildwheel/_vendored/pep514tools/__init__.py @@ -0,0 +1,11 @@ +#------------------------------------------------------------------------- +# Copyright (c) Steve Dower +# All rights reserved. +# +# Distributed under the terms of the MIT License +#------------------------------------------------------------------------- + +__author__ = 'Steve Dower ' +__version__ = '0.1.0' + +from .environment import findall, find, findone diff --git a/cibuildwheel/_vendored/pep514tools/__main__.py b/cibuildwheel/_vendored/pep514tools/__main__.py new file mode 100755 index 000000000..e7f1e8bfe --- /dev/null +++ b/cibuildwheel/_vendored/pep514tools/__main__.py @@ -0,0 +1,7 @@ +#------------------------------------------------------------------------- +# Copyright (c) Steve Dower +# All rights reserved. +# +# Distributed under the terms of the MIT License +#------------------------------------------------------------------------- + diff --git a/cibuildwheel/_vendored/pep514tools/_registry.py b/cibuildwheel/_vendored/pep514tools/_registry.py new file mode 100755 index 000000000..fd0bc6722 --- /dev/null +++ b/cibuildwheel/_vendored/pep514tools/_registry.py @@ -0,0 +1,198 @@ +#------------------------------------------------------------------------- +# Copyright (c) Steve Dower +# All rights reserved. +# +# Distributed under the terms of the MIT License +#------------------------------------------------------------------------- + +__all__ = ['open_source', 'REGISTRY_SOURCE_LM', 'REGISTRY_SOURCE_LM_WOW6432', 'REGISTRY_SOURCE_CU'] + +from itertools import count +import re +try: + import winreg +except ImportError: + import _winreg as winreg + +REGISTRY_SOURCE_LM = 1 +REGISTRY_SOURCE_LM_WOW6432 = 2 +REGISTRY_SOURCE_CU = 3 + +_REG_KEY_INFO = { + REGISTRY_SOURCE_LM: (winreg.HKEY_LOCAL_MACHINE, r'Software\Python', winreg.KEY_WOW64_64KEY), + REGISTRY_SOURCE_LM_WOW6432: (winreg.HKEY_LOCAL_MACHINE, r'Software\Python', winreg.KEY_WOW64_32KEY), + REGISTRY_SOURCE_CU: (winreg.HKEY_CURRENT_USER, r'Software\Python', 0), +} + +def get_value_from_tuple(value, vtype): + if vtype == winreg.REG_SZ: + if '\0' in value: + return value[:value.index('\0')] + return value + return None + +def join(x, y): + return x + '\\' + y + +_VALID_ATTR = re.compile('^[a-z_]+$') +_VALID_KEY = re.compile('^[A-Za-z]+$') +_KEY_TO_ATTR = re.compile('([A-Z]+[a-z]+)') + +class PythonWrappedDict(object): + @staticmethod + def _attr_to_key(attr): + if not attr: + return '' + if not _VALID_ATTR.match(attr): + return attr + return ''.join(c.capitalize() for c in attr.split('_')) + + @staticmethod + def _key_to_attr(key): + if not key: + return '' + if not _VALID_KEY.match(key): + return key + return '_'.join(k for k in _KEY_TO_ATTR.split(key) if k).lower() + + def __init__(self, d): + self._d = d + + def __getattr__(self, attr): + if attr.startswith('_'): + return object.__getattribute__(self, attr) + + if attr == 'value': + attr = '' + + key = self._attr_to_key(attr) + try: + return self._d[key] + except KeyError: + pass + except Exception: + raise AttributeError(attr) + raise AttributeError(attr) + + def __setattr__(self, attr, value): + if attr.startswith('_'): + return object.__setattr__(self, attr, value) + + if attr == 'value': + attr = '' + self._d[self._attr_to_key(attr)] = value + + def __dir__(self): + k2a = self._key_to_attr + return list(map(k2a, self._d)) + + def _setdefault(self, key, value): + self._d.setdefault(key, value) + + def _items(self): + return self._d.items() + + def __repr__(self): + k2a = self._key_to_attr + return 'info(' + ', '.join('{}={!r}'.format(k2a(k), v) for k, v in self._d.items()) + ')' + +class RegistryAccessor(object): + def __init__(self, root, subkey, flags): + self._root = root + self.subkey = subkey + _, _, self.name = subkey.rpartition('\\') + self._flags = flags + + def __iter__(self): + subkey_names = [] + try: + with winreg.OpenKeyEx(self._root, self.subkey, 0, winreg.KEY_READ | self._flags) as key: + for i in count(): + subkey_names.append(winreg.EnumKey(key, i)) + except OSError: + pass + return iter(self[k] for k in subkey_names) + + def __getitem__(self, key): + return RegistryAccessor(self._root, join(self.subkey, key), self._flags) + + def get_value(self, value_name): + try: + with winreg.OpenKeyEx(self._root, self.subkey, 0, winreg.KEY_READ | self._flags) as key: + return get_value_from_tuple(*winreg.QueryValueEx(key, value_name)) + except OSError: + return None + + def get_all_values(self): + schema = {} + for subkey in self: + schema[subkey.name] = subkey.get_all_values() + + key = winreg.OpenKeyEx(self._root, self.subkey, 0, winreg.KEY_READ | self._flags) + try: + with key: + for i in count(): + vname, value, vtype = winreg.EnumValue(key, i) + value = get_value_from_tuple(value, vtype) + if value: + schema[vname or ''] = value + except OSError: + pass + + return PythonWrappedDict(schema) + + def set_value(self, value_name, value): + with winreg.CreateKeyEx(self._root, self.subkey, 0, winreg.KEY_WRITE | self._flags) as key: + if value is None: + winreg.DeleteValue(key, value_name) + elif isinstance(value, str): + winreg.SetValueEx(key, value_name, 0, winreg.REG_SZ, value) + else: + raise TypeError('cannot write {} to registry'.format(type(value))) + + def _set_all_values(self, rootkey, name, info, errors): + with winreg.CreateKeyEx(rootkey, name, 0, winreg.KEY_WRITE | self._flags) as key: + for k, v in info: + if isinstance(v, PythonWrappedDict): + self._set_all_values(key, k, v._items(), errors) + elif isinstance(v, dict): + self._set_all_values(key, k, v.items(), errors) + elif v is None: + winreg.DeleteValue(key, k) + elif isinstance(v, str): + winreg.SetValueEx(key, k, 0, winreg.REG_SZ, v) + else: + errors.append('cannot write {} to registry'.format(type(v))) + + def set_all_values(self, info): + errors = [] + if isinstance(info, PythonWrappedDict): + items = info._items() + elif isinstance(info, dict): + items = info.items() + else: + raise TypeError('info must be a dictionary') + + self._set_all_values(self._root, self.subkey, items, errors) + if len(errors) == 1: + raise ValueError(errors[0]) + elif errors: + raise ValueError(errors) + + def delete(self): + for k in self: + k.delete() + try: + key = winreg.OpenKeyEx(self._root, None, 0, winreg.KEY_READ | self._flags) + except OSError: + return + with key: + winreg.DeleteKeyEx(key, self.subkey) + + +def open_source(registry_source): + info = _REG_KEY_INFO.get(registry_source) + if not info: + raise ValueError("unsupported registry source") + root, subkey, flags = info + return RegistryAccessor(root, subkey, flags) diff --git a/cibuildwheel/_vendored/pep514tools/environment.py b/cibuildwheel/_vendored/pep514tools/environment.py new file mode 100755 index 000000000..9986f20fe --- /dev/null +++ b/cibuildwheel/_vendored/pep514tools/environment.py @@ -0,0 +1,123 @@ +#------------------------------------------------------------------------- +# Copyright (c) Steve Dower +# All rights reserved. +# +# Distributed under the terms of the MIT License +#------------------------------------------------------------------------- + +__all__ = ['Environment', 'findall', 'find', 'findone'] + +from itertools import count +from ._registry import open_source, REGISTRY_SOURCE_LM, REGISTRY_SOURCE_LM_WOW6432, REGISTRY_SOURCE_CU +import re +import sys + +# These tags are treated specially when the Company is 'PythonCore' +_PYTHONCORE_COMPATIBILITY_TAGS = { + '2.0', '2.1', '2.2', '2.3', '2.4', '2.5', '2.6', '2.7', + '3.0', '3.1', '3.2', '3.3', '3.4' +} + +_IS_64BIT_OS = None +def _is_64bit_os(): + global _IS_64BIT_OS + if _IS_64BIT_OS is None: + if sys.maxsize > 2**32: + import platform + _IS_64BIT_OS = (platform.machine() == 'AMD64') + else: + _IS_64BIT_OS = False + return _IS_64BIT_OS + +class Environment(object): + def __init__(self, source, company, tag, guessed_arch=None): + self._source = source + self.company = company + self.tag = tag + self._guessed_arch = guessed_arch + self._orig_info = company, tag + self.info = {} + + def load(self): + if not self._source: + raise ValueError('Environment not initialized with a source') + self.info = info = self._source[self.company][self.tag].get_all_values() + if self.company == 'PythonCore': + info._setdefault('DisplayName', 'Python ' + self.tag) + info._setdefault('SupportUrl', 'http://www.python.org/') + info._setdefault('Version', self.tag[:3]) + info._setdefault('SysVersion', self.tag[:3]) + if self._guessed_arch: + info._setdefault('SysArchitecture', self._guessed_arch) + + def save(self, copy=False): + if not self._source: + raise ValueError('Environment not initialized with a source') + if (self.company, self.tag) != self._orig_info: + if not copy: + self._source[self._orig_info[0]][self._orig_info[1]].delete() + self._orig_info = self.company, self.tag + + src = self._source[self.company][self.tag] + src.set_all_values(self.info) + + self.info = src.get_all_values() + + def delete(self): + if (self.company, self.tag) != self._orig_info: + raise ValueError("cannot delete Environment when company/tag have been modified") + + if not self._source: + raise ValueError('Environment not initialized with a source') + self._source.delete() + + def __repr__(self): + return ''.format(self.company, self.tag) + +def _get_sources(include_per_machine=True, include_per_user=True): + if _is_64bit_os(): + if include_per_user: + yield open_source(REGISTRY_SOURCE_CU), None + if include_per_machine: + yield open_source(REGISTRY_SOURCE_LM), '64bit' + yield open_source(REGISTRY_SOURCE_LM_WOW6432), '32bit' + else: + if include_per_user: + yield open_source(REGISTRY_SOURCE_CU), '32bit' + if include_per_machine: + yield open_source(REGISTRY_SOURCE_LM), '32bit' + +def findall(include_per_machine=True, include_per_user=True): + for src, arch in _get_sources(include_per_machine=include_per_machine, include_per_user=include_per_user): + for company in src: + for tag in company: + try: + env = Environment(src, company.name, tag.name, arch) + env.load() + except OSError: + pass + else: + yield env + +def find(company_or_tag, tag=None, include_per_machine=True, include_per_user=True, maxcount=None): + if not tag: + env = Environment(None, 'PythonCore', company_or_tag) + else: + env = Environment(None, company_or_tag, tag) + + results = [] + for src, arch in _get_sources(include_per_machine=include_per_machine, include_per_user=include_per_user): + try: + env._source = src + env._guessed_arch = arch + env.load() + except OSError: + pass + else: + results.append(env) + return results + +def findone(company_or_tag, tag=None, include_per_machine=True, include_per_user=True): + found = find(company_or_tag, tag, include_per_machine, include_per_user, maxcount=1) + if found: + return found[0] diff --git a/cibuildwheel/linux.py b/cibuildwheel/linux.py index 233f1f024..d110b97c1 100644 --- a/cibuildwheel/linux.py +++ b/cibuildwheel/linux.py @@ -18,12 +18,14 @@ def get_python_configurations(build_selector): PythonConfiguration(identifier='cp35-manylinux_x86_64', path='/opt/python/cp35-cp35m'), PythonConfiguration(identifier='cp36-manylinux_x86_64', path='/opt/python/cp36-cp36m'), PythonConfiguration(identifier='cp37-manylinux_x86_64', path='/opt/python/cp37-cp37m'), + PythonConfiguration(identifier='cp38-manylinux_x86_64', path='/opt/python/cp38-cp38'), PythonConfiguration(identifier='cp27-manylinux_i686', path='/opt/python/cp27-cp27m'), PythonConfiguration(identifier='cp27-manylinux_i686', path='/opt/python/cp27-cp27mu'), PythonConfiguration(identifier='cp34-manylinux_i686', path='/opt/python/cp34-cp34m'), PythonConfiguration(identifier='cp35-manylinux_i686', path='/opt/python/cp35-cp35m'), PythonConfiguration(identifier='cp36-manylinux_i686', path='/opt/python/cp36-cp36m'), PythonConfiguration(identifier='cp37-manylinux_i686', path='/opt/python/cp37-cp37m'), + PythonConfiguration(identifier='cp38-manylinux_i686', path='/opt/python/cp38-cp38'), ] # skip builds as required diff --git a/cibuildwheel/macos.py b/cibuildwheel/macos.py index 7378f1421..88e993f9c 100644 --- a/cibuildwheel/macos.py +++ b/cibuildwheel/macos.py @@ -19,6 +19,7 @@ def get_python_configurations(build_selector): PythonConfiguration(version='3.5', identifier='cp35-macosx_10_6_intel', url='https://www.python.org/ftp/python/3.5.4/python-3.5.4-macosx10.6.pkg'), PythonConfiguration(version='3.6', identifier='cp36-macosx_10_6_intel', url='https://www.python.org/ftp/python/3.6.8/python-3.6.8-macosx10.6.pkg'), PythonConfiguration(version='3.7', identifier='cp37-macosx_10_6_intel', url='https://www.python.org/ftp/python/3.7.5/python-3.7.5-macosx10.6.pkg'), + PythonConfiguration(version='3.8', identifier='cp38-macosx_10_9_x86_64', url='https://www.python.org/ftp/python/3.8.0/python-3.8.0-macosx10.9.pkg'), ] # skip builds as required diff --git a/cibuildwheel/windows.py b/cibuildwheel/windows.py index da929bcc4..0be5a1d86 100644 --- a/cibuildwheel/windows.py +++ b/cibuildwheel/windows.py @@ -1,6 +1,7 @@ from __future__ import print_function import os, tempfile, subprocess, shutil, sys from collections import namedtuple +from contextlib import contextmanager from glob import glob try: @@ -8,84 +9,123 @@ except ImportError: from pipes import quote as shlex_quote +try: + from urllib.request import urlopen +except ImportError: + from urllib2 import urlopen + from .util import prepare_command, get_build_verbosity_extra_flags +if hasattr(sys, 'getwindowsversion'): + from ._vendored.pep514tools import findone as _pep514_findone IS_RUNNING_ON_AZURE = os.path.exists('C:\\hostedtoolcache') IS_RUNNING_ON_TRAVIS = os.environ.get('TRAVIS_OS_NAME') == 'windows' +IS_RUNNING_ON_APPVEYOR = os.environ.get('APPVEYOR', 'false').lower() == 'true' + + +@contextmanager +def _mkdtemp(): + path = tempfile.mkdtemp() + try: + yield path + finally: + shutil.rmtree(path) + def get_python_path(config): - if IS_RUNNING_ON_AZURE: - # We can't hard-code the paths because on Azure, we don't know which - # bugfix release of Python we are getting so we need to check which - # ones exist. We just use the first one that is found since there should - # only be one. - path_pattern = 'C:\\hostedtoolcache\\windows\\Python\\{version}\\{arch}'.format( - version=config.version.replace('x', '*'), - arch='x86' if config.arch == '32' else 'x64' - ) - try: - return glob(path_pattern)[0] - except IndexError: - raise Exception('Could not find a Python install at ' + path_pattern) - elif IS_RUNNING_ON_TRAVIS: - if config.version == "3.4.x": - return config.path - else: - nuget_args = get_nuget_args(config) - return os.path.join(nuget_args[-1], nuget_args[0] + "." + config.nuget_version, "tools") + major, minor = config.version.split('.')[:2] + + if (int(major), int(minor)) < (3, 5): + if IS_RUNNING_ON_AZURE: + # We can't hard-code the paths because on Azure, we don't know which + # bugfix release of Python we are getting so we need to check which + # ones exist. We just use the first one that is found since there should + # only be one. + path_pattern = 'C:\\hostedtoolcache\\windows\\Python\\{version}\\{arch}'.format( + version=config.version.replace('x', '*'), + arch='x86' if config.arch == '32' else 'x64' + ) + matches = glob(path_pattern) + if len(matches) > 0: + return matches[0] + elif IS_RUNNING_ON_APPVEYOR: + # We're running on AppVeyor + major, minor = config.version.split('.')[:2] + python_path = 'C:\\Python{major}{minor}{arch}'.format( + major=major, + minor=minor, + arch = '-x64' if config.arch == '64' else '' + ) + if os.path.exists(python_path): + return python_path else: - # Assume we're running on AppVeyor - major, minor = config.version.split('.')[:2] - return 'C:\\Python{major}{minor}{arch}'.format( + tag = '{major}.{minor}{arch}'.format( major=major, minor=minor, - arch = '-x64' if config.arch == '64' else '' + arch = '-32' if config.arch == '32' else '' ) + python_install = _pep514_findone('PythonCore', tag) + if python_install and hasattr(python_install.info, 'install_path'): + return str(getattr(python_install.info.install_path, '')) + python_path = 'C:\\cibuildwheel\\Python{major}{minor}{arch}'.format( + major=major, + minor=minor, + arch = '-x64' if config.arch == '64' else '' + ) + return python_path -def get_nuget_args(configuration): - if configuration.nuget_version is None: - return None - python_name = "python" if configuration.version[0] == '3' else "python2" - if configuration.arch == "32": - python_name = python_name + "x86" - return [python_name, "-Version", configuration.nuget_version, "-OutputDirectory", "C:/python"] - def get_python_configurations(build_selector): - PythonConfiguration = namedtuple('PythonConfiguration', ['version', 'arch', 'identifier', 'path', "nuget_version"]) + PythonConfiguration = namedtuple('PythonConfiguration', ['version', 'arch', 'identifier', 'url']) python_configurations = [ - PythonConfiguration(version='2.7.x', arch="32", identifier='cp27-win32', path='C:\\Python27', nuget_version="2.7.16"), - PythonConfiguration(version='2.7.x', arch="64", identifier='cp27-win_amd64', path='C:\\Python27-x64', nuget_version="2.7.16"), - PythonConfiguration(version='3.4.x', arch="32", identifier='cp34-win32', path='C:\\Python34', nuget_version=None), - PythonConfiguration(version='3.4.x', arch="64", identifier='cp34-win_amd64', path='C:\\Python34-x64', nuget_version=None), - PythonConfiguration(version='3.5.x', arch="32", identifier='cp35-win32', path='C:\\Python35', nuget_version="3.5.4"), - PythonConfiguration(version='3.5.x', arch="64", identifier='cp35-win_amd64', path='C:\\Python35-x64', nuget_version="3.5.4"), - PythonConfiguration(version='3.6.x', arch="32", identifier='cp36-win32', path='C:\\Python36', nuget_version="3.6.8"), - PythonConfiguration(version='3.6.x', arch="64", identifier='cp36-win_amd64', path='C:\\Python36-x64', nuget_version="3.6.8"), - PythonConfiguration(version='3.7.x', arch="32", identifier='cp37-win32', path='C:\\Python37', nuget_version="3.7.4"), - PythonConfiguration(version='3.7.x', arch="64", identifier='cp37-win_amd64', path='C:\\Python37-x64', nuget_version="3.7.4") + PythonConfiguration(version='2.7.x', arch="32", identifier='cp27-win32', url=None), + PythonConfiguration(version='2.7.x', arch="64", identifier='cp27-win_amd64', url=None), + PythonConfiguration(version='3.4.x', arch="32", identifier='cp34-win32', url=None), + PythonConfiguration(version='3.4.x', arch="64", identifier='cp34-win_amd64', url=None), + PythonConfiguration(version='3.5.x', arch="32", identifier='cp35-win32', url='https://www.python.org/ftp/python/3.5.4/python-3.5.4.exe'), + PythonConfiguration(version='3.5.x', arch="64", identifier='cp35-win_amd64', url='https://www.python.org/ftp/python/3.5.4/python-3.5.4-amd64.exe'), + PythonConfiguration(version='3.6.x', arch="32", identifier='cp36-win32', url='https://www.python.org/ftp/python/3.6.8/python-3.6.8.exe'), + PythonConfiguration(version='3.6.x', arch="64", identifier='cp36-win_amd64', url='https://www.python.org/ftp/python/3.6.8/python-3.6.8-amd64.exe'), + PythonConfiguration(version='3.7.x', arch="32", identifier='cp37-win32', url='https://www.python.org/ftp/python/3.7.5/python-3.7.5.exe'), + PythonConfiguration(version='3.7.x', arch="64", identifier='cp37-win_amd64', url='https://www.python.org/ftp/python/3.7.5/python-3.7.5-amd64.exe'), + PythonConfiguration(version='3.8.x', arch="32", identifier='cp38-win32', url='https://www.python.org/ftp/python/3.8.0/python-3.8.0.exe'), + PythonConfiguration(version='3.8.x', arch="64", identifier='cp38-win_amd64', url='https://www.python.org/ftp/python/3.8.0/python-3.8.0-amd64.exe'), ] if IS_RUNNING_ON_AZURE: # Python 3.4 isn't supported on Azure. # See https://github.com/Microsoft/azure-pipelines-tasks/issues/9674 python_configurations = [c for c in python_configurations if c.version != '3.4.x'] - + if IS_RUNNING_ON_TRAVIS: # cannot install VCForPython27.msi which is needed for compiling C software # try with (and similar): msiexec /i VCForPython27.msi ALLUSERS=1 ACCEPT=YES /passive - # no easy and stable way fo installing python 3.4 + # no easy and stable way fo installing python 3.4 python_configurations = [c for c in python_configurations if c.version != '2.7.x' and c.version != '3.4.x'] # skip builds as required python_configurations = [c for c in python_configurations if build_selector(c.identifier)] - + return python_configurations def build(project_dir, output_dir, test_command, test_requires, test_extras, before_build, build_verbosity, build_selector, environment): + def install(url, path): + with _mkdtemp() as tempdir: + fname = os.path.basename(url) + installer = os.path.join(tempdir, fname) + print('+ Download ' + url + ' to ' + installer) + response = urlopen(url) + try: + with open(installer, 'wb') as file: + file.write(response.read()) + finally: + response.close() + print('+ Installing ' + installer + ' to ' + path) + args = ['cmd', '/E:ON', '/V:ON', '/C', 'start', '/wait', installer, '/quiet', 'TargetDir=' + path, 'Shortcuts=0', 'Include_launcher=0', 'InstallLauncherAllUsers=0'] + return subprocess.check_call(args) if IS_RUNNING_ON_AZURE or IS_RUNNING_ON_TRAVIS: def shell(args, env=None, cwd=None): print('+ ' + ' '.join(args)) @@ -106,21 +146,11 @@ def shell(args, env=None, cwd=None): temp_dir = tempfile.mkdtemp(prefix='cibuildwheel') built_wheel_dir = os.path.join(temp_dir, 'built_wheel') - if IS_RUNNING_ON_TRAVIS: - # instal nuget as best way for provide python - shell(["choco", "install", "nuget.commandline"]) - # get pip fo this installation which not have. - get_pip_url = 'https://bootstrap.pypa.io/get-pip.py' - get_pip_script = 'C:\\get-pip.py' - shell(['curl', '-L', '-o', get_pip_script, get_pip_url]) - python_configurations = get_python_configurations(build_selector) for config in python_configurations: config_python_path = get_python_path(config) - if IS_RUNNING_ON_TRAVIS and config.nuget_version is not None and not os.path.exists(config_python_path): - shell(["nuget", "install"] + get_nuget_args(config)) - if not os.path.exists(os.path.join(config_python_path, 'Scripts', 'pip.exe')): - shell([os.path.join(config_python_path, 'python.exe'), get_pip_script ]) + if not os.path.exists(config_python_path): + install(config.url, config_python_path) # check python & pip exist for this configuration assert os.path.exists(os.path.join(config_python_path, 'python.exe')) diff --git a/test/shared/utils.py b/test/shared/utils.py index 78f09050e..6fed311d3 100644 --- a/test/shared/utils.py +++ b/test/shared/utils.py @@ -26,14 +26,14 @@ def cibuildwheel_get_build_identifiers(project_path, env=None): def cibuildwheel_run(project_path, env=None, add_env=None): ''' - Runs cibuildwheel as a subprocess, building the project at project_path. - + Runs cibuildwheel as a subprocess, building the project at project_path. + Uses the current Python interpreter. Configure settings using env. ''' if env is None: env = os.environ.copy() - + if add_env is not None: env.update(add_env) @@ -55,24 +55,28 @@ def expected_wheels(package_name, package_version): '{package_name}-{package_version}-cp35-cp35m-manylinux1_x86_64.whl', '{package_name}-{package_version}-cp36-cp36m-manylinux1_x86_64.whl', '{package_name}-{package_version}-cp37-cp37m-manylinux1_x86_64.whl', + '{package_name}-{package_version}-cp38-cp38-manylinux1_x86_64.whl', '{package_name}-{package_version}-cp27-cp27m-manylinux2010_x86_64.whl', '{package_name}-{package_version}-cp27-cp27mu-manylinux2010_x86_64.whl', '{package_name}-{package_version}-cp34-cp34m-manylinux2010_x86_64.whl', '{package_name}-{package_version}-cp35-cp35m-manylinux2010_x86_64.whl', '{package_name}-{package_version}-cp36-cp36m-manylinux2010_x86_64.whl', '{package_name}-{package_version}-cp37-cp37m-manylinux2010_x86_64.whl', + '{package_name}-{package_version}-cp38-cp38-manylinux2010_x86_64.whl', '{package_name}-{package_version}-cp27-cp27m-manylinux1_i686.whl', '{package_name}-{package_version}-cp27-cp27mu-manylinux1_i686.whl', '{package_name}-{package_version}-cp34-cp34m-manylinux1_i686.whl', '{package_name}-{package_version}-cp35-cp35m-manylinux1_i686.whl', '{package_name}-{package_version}-cp36-cp36m-manylinux1_i686.whl', '{package_name}-{package_version}-cp37-cp37m-manylinux1_i686.whl', + '{package_name}-{package_version}-cp38-cp38-manylinux1_i686.whl', '{package_name}-{package_version}-cp27-cp27m-manylinux2010_i686.whl', '{package_name}-{package_version}-cp27-cp27mu-manylinux2010_i686.whl', '{package_name}-{package_version}-cp34-cp34m-manylinux2010_i686.whl', '{package_name}-{package_version}-cp35-cp35m-manylinux2010_i686.whl', '{package_name}-{package_version}-cp36-cp36m-manylinux2010_i686.whl', '{package_name}-{package_version}-cp37-cp37m-manylinux2010_i686.whl', + '{package_name}-{package_version}-cp38-cp38-manylinux2010_i686.whl', ] elif platform == 'windows': templates = [ @@ -81,11 +85,13 @@ def expected_wheels(package_name, package_version): '{package_name}-{package_version}-cp35-cp35m-win32.whl', '{package_name}-{package_version}-cp36-cp36m-win32.whl', '{package_name}-{package_version}-cp37-cp37m-win32.whl', + '{package_name}-{package_version}-cp38-cp38-win32.whl', '{package_name}-{package_version}-cp27-cp27m-win_amd64.whl', '{package_name}-{package_version}-cp34-cp34m-win_amd64.whl', '{package_name}-{package_version}-cp35-cp35m-win_amd64.whl', '{package_name}-{package_version}-cp36-cp36m-win_amd64.whl', '{package_name}-{package_version}-cp37-cp37m-win_amd64.whl', + '{package_name}-{package_version}-cp38-cp38-win_amd64.whl', ] elif platform == 'macos': templates = [ @@ -94,17 +100,18 @@ def expected_wheels(package_name, package_version): '{package_name}-{package_version}-cp35-cp35m-macosx_10_6_intel.whl', '{package_name}-{package_version}-cp36-cp36m-macosx_10_6_intel.whl', '{package_name}-{package_version}-cp37-cp37m-macosx_10_6_intel.whl', + '{package_name}-{package_version}-cp38-cp38-macosx_10_9_x86_64.whl', ] else: raise Exception('unsupported platform') - + if IS_WINDOWS_RUNNING_ON_AZURE: # Python 3.4 isn't supported on Azure. templates = [t for t in templates if '-cp34-' not in t] if IS_WINDOWS_RUNNING_ON_TRAVIS: # Python 2.7 and 3.4 isn't supported on Travis. templates = [t for t in templates if '-cp27-' not in t and '-cp34-' not in t] - + return [filename.format(package_name=package_name, package_version=package_version) for filename in templates]