From b24b7276295a6617bd4634e5d09917263d4aefea Mon Sep 17 00:00:00 2001 From: mayeut Date: Sat, 26 Oct 2019 13:45:09 +0200 Subject: [PATCH] Install python with official installer if an official install is not found Vendor-In pep514tools --- .travis.yml | 6 +- appveyor.yml | 2 +- azure-pipelines.yml | 12 +- cibuildwheel/_vendored/__init__.py | 0 cibuildwheel/_vendored/pep514tools/LICENSE | 21 ++ .../_vendored/pep514tools/__init__.py | 11 + .../_vendored/pep514tools/__main__.py | 7 + .../_vendored/pep514tools/_registry.py | 198 ++++++++++++++++++ .../_vendored/pep514tools/environment.py | 123 +++++++++++ cibuildwheel/windows.py | 144 ++++++++----- test/shared/utils.py | 12 +- 11 files changed, 461 insertions(+), 75 deletions(-) create mode 100644 cibuildwheel/_vendored/__init__.py create mode 100755 cibuildwheel/_vendored/pep514tools/LICENSE create mode 100755 cibuildwheel/_vendored/pep514tools/__init__.py create mode 100755 cibuildwheel/_vendored/pep514tools/__main__.py create mode 100755 cibuildwheel/_vendored/pep514tools/_registry.py create mode 100755 cibuildwheel/_vendored/pep514tools/environment.py 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/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/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..89ea83994 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) @@ -81,11 +81,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 = [ @@ -97,14 +99,14 @@ def expected_wheels(package_name, package_version): ] 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]