from __future__ import print_function import os, tempfile, subprocess, shutil, sys from collections import namedtuple from glob import glob try: from shlex import quote as shlex_quote 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 IS_RUNNING_ON_AZURE = os.path.exists('C:\\hostedtoolcache') IS_RUNNING_ON_TRAVIS = os.environ.get('TRAVIS_OS_NAME') == 'windows' def get_python_path(config): nuget_args = get_nuget_args(config) return os.path.join(nuget_args[-1], nuget_args[0] + "." + config.version, "tools") def get_nuget_args(configuration): python_name = "python" if configuration.version[0] == '3' else "python2" if configuration.arch == "32": python_name = python_name + "x86" return [python_name, "-Version", configuration.version, "-OutputDirectory", "C:/python"] def get_python_configurations(build_selector): PythonConfiguration = namedtuple('PythonConfiguration', ['version', 'arch', 'identifier']) python_configurations = [ PythonConfiguration(version='2.7.17', arch="32", identifier='cp27-win32'), PythonConfiguration(version='2.7.17', arch="64", identifier='cp27-win_amd64'), PythonConfiguration(version='3.5.4', arch="32", identifier='cp35-win32'), PythonConfiguration(version='3.5.4', arch="64", identifier='cp35-win_amd64'), PythonConfiguration(version='3.6.8', arch="32", identifier='cp36-win32'), PythonConfiguration(version='3.6.8', arch="64", identifier='cp36-win_amd64'), PythonConfiguration(version='3.7.5', arch="32", identifier='cp37-win32'), PythonConfiguration(version='3.7.5', arch="64", identifier='cp37-win_amd64'), PythonConfiguration(version='3.8.0', arch="32", identifier='cp38-win32'), PythonConfiguration(version='3.8.0', arch="64", identifier='cp38-win_amd64'), ] 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 python_configurations = [c for c in python_configurations if not c.version.startswith('2.7.')] # 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 simple_shell(args, env=None, cwd=None): print('+ ' + ' '.join(args)) args = ['cmd', '/E:ON', '/V:ON', '/C'] + args return subprocess.check_call(' '.join(args), env=env, cwd=cwd) def download(url, dest): print('+ Download ' + url + ' to ' + dest) response = urlopen(url) try: with open(dest, 'wb') as file: file.write(response.read()) finally: response.close() if IS_RUNNING_ON_AZURE or IS_RUNNING_ON_TRAVIS: shell = simple_shell else: run_with_env = os.path.abspath(os.path.join(os.path.dirname(__file__), 'resources', 'appveyor_run_with_env.cmd')) # run_with_env is a cmd file that sets the right environment variables # to build on AppVeyor. 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) abs_project_dir = os.path.abspath(project_dir) temp_dir = tempfile.mkdtemp(prefix='cibuildwheel') built_wheel_dir = os.path.join(temp_dir, 'built_wheel') # install nuget as best way to provide python nuget = 'C:\\nuget.exe' download('https://dist.nuget.org/win-x86-commandline/latest/nuget.exe', nuget) # get pip fo this installation which not have. get_pip_script = 'C:\\get-pip.py' download('https://bootstrap.pypa.io/get-pip.py', get_pip_script) python_configurations = get_python_configurations(build_selector) for config in python_configurations: config_python_path = get_python_path(config) simple_shell([nuget, "install"] + get_nuget_args(config)) if not os.path.exists(os.path.join(config_python_path, 'Scripts', 'pip.exe')): simple_shell([os.path.join(config_python_path, 'python.exe'), get_pip_script ]) # check python & pip exist for this configuration assert os.path.exists(os.path.join(config_python_path, 'python.exe')) assert os.path.exists(os.path.join(config_python_path, 'Scripts', 'pip.exe')) # setup dirs if os.path.exists(built_wheel_dir): shutil.rmtree(built_wheel_dir) os.makedirs(built_wheel_dir) 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_python_path, os.path.join(config_python_path, 'Scripts'), env['PATH'] ]) env = environment.as_dictionary(prev_environment=env) # 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(['python', '-m', 'pip', 'install', '--upgrade', 'pip'], env=env) shell(['pip', 'install', '--upgrade', 'setuptools'], env=env) shell(['pip', 'install', 'wheel'], env=env) # run the before_build command if before_build: before_build_prepared = prepare_command(before_build, project=abs_project_dir) shell([before_build_prepared], env=env) # build the wheel shell(['pip', 'wheel', abs_project_dir, '-w', built_wheel_dir, '--no-deps'] + get_build_verbosity_extra_flags(build_verbosity), env=env) built_wheel = glob(built_wheel_dir+'/*.whl')[0] if test_command: # set up a virtual environment to install and test from, to make sure # there are no dependencies that were pulled in at build time. shell(['pip', 'install', 'virtualenv'], env=env) venv_dir = tempfile.mkdtemp() shell(['python', '-m', 'virtualenv', venv_dir], env=env) virtualenv_env = env.copy() virtualenv_env['PATH'] = os.pathsep.join([ os.path.join(venv_dir, 'Scripts'), virtualenv_env['PATH'], ]) # check that we are using the Python from the virtual environment shell(['which', 'python'], env=virtualenv_env) # install the wheel shell(['pip', 'install', built_wheel + test_extras], env=virtualenv_env) # test the wheel if test_requires: shell(['pip', 'install'] + test_requires, env=virtualenv_env) # run the tests from c:\, with an absolute path in the command # (this ensures that Python runs the tests against the installed wheel # and not the repo code) test_command_prepared = prepare_command(test_command, project=abs_project_dir) shell([test_command_prepared], cwd='c:\\', env=virtualenv_env) # clean up shutil.rmtree(venv_dir) # we're all done here; move it to output (remove if already exists) dst = os.path.join(output_dir, os.path.basename(built_wheel)) if os.path.isfile(dst): os.remove(dst) shutil.move(built_wheel, dst)