#!/usr/bin/env python2 # vim:fileencoding=utf-8 # License: GPLv3 Copyright: 2016, Kovid Goyal from __future__ import absolute_import, division, print_function, unicode_literals import io import os import subprocess import sys import tarfile import time if sys.version_info.major > 2: import winreg else: try: import _winreg as winreg except ImportError: import winreg is64bit = os.environ.get('PLATFORM') != 'x86' def vcvars(): # {{{ RegOpenKeyEx = winreg.OpenKeyEx RegEnumValue = winreg.EnumValue RegError = winreg.error HKEYS = ( winreg.HKEY_USERS, winreg.HKEY_CURRENT_USER, winreg.HKEY_LOCAL_MACHINE, winreg.HKEY_CLASSES_ROOT ) VS_BASE = r"Software\Wow6432Node\Microsoft\VisualStudio\%0.1f" def get_reg_value(path, key): for base in HKEYS: d = read_values(base, path) if d and key in d: return d[key] raise KeyError(key) def convert_mbcs(s): dec = getattr(s, "decode", None) if dec is not None: try: s = dec("mbcs") except UnicodeError: pass return s def read_values(base, key): """Return dict of registry keys and values. All names are converted to lowercase. """ try: handle = RegOpenKeyEx(base, key) except RegError: return None d = {} i = 0 while True: try: name, value, type = RegEnumValue(handle, i) except RegError: break name = name.lower() d[convert_mbcs(name)] = convert_mbcs(value) i += 1 return d def find_vcvarsall(version=14.0): vsbase = VS_BASE % version try: productdir = get_reg_value(r"%s\Setup\VC" % vsbase, "productdir") except KeyError: raise SystemExit( "Unable to find Visual Studio product directory in the registry" ) if not productdir: raise SystemExit("No productdir found") vcvarsall = os.path.join(productdir, "vcvarsall.bat") if os.path.isfile(vcvarsall): return vcvarsall raise SystemExit("Unable to find vcvarsall.bat in productdir: " + productdir) def remove_dups(variable): old_list = variable.split(os.pathsep) new_list = [] for i in old_list: if i not in new_list: new_list.append(i) return os.pathsep.join(new_list) def query_process(cmd): if is64bit and 'PROGRAMFILES(x86)' not in os.environ: os.environ['PROGRAMFILES(x86)'] = os.environ['PROGRAMFILES'] + ' (x86)' result = {} popen = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) try: stdout, stderr = popen.communicate() if popen.wait() != 0: raise RuntimeError(stderr.decode("mbcs")) stdout = stdout.decode("mbcs") for line in stdout.splitlines(): if '=' not in line: continue line = line.strip() key, value = line.split('=', 1) key = key.lower() if key == 'path': if value.endswith(os.pathsep): value = value[:-1] value = remove_dups(value) result[key] = value finally: popen.stdout.close() popen.stderr.close() return result def query_vcvarsall(): plat = 'amd64' if is64bit else 'x86' vcvarsall = find_vcvarsall() env = query_process('"%s" %s & set' % (vcvarsall, plat)) def g(k): try: return env[k] except KeyError: return env[k.lower()] # We have to insert the correct path to MSBuild.exe so that the one # from the .net frameworks is not used. paths = g('PATH').split(os.pathsep) for i, p in enumerate(tuple(paths)): if os.path.exists(os.path.join(p, 'MSBuild.exe')): if '.net' in p.lower(): paths.insert( i, r'C:\Program Files (x86)\MSBuild\14.0\bin' + (r'\amd64' if is64bit else '') ) env["PATH"] = os.pathsep.join(paths) break return { k: g(k) for k in 'PATH LIB INCLUDE LIBPATH WINDOWSSDKDIR VS140COMNTOOLS UCRTVERSION UNIVERSALCRTSDKDIR'. split() } return query_vcvarsall() # }}} def printf(*args, **kw): print(*args, **kw) sys.stdout.flush() def download_file(url): for i in range(5): try: printf('Downloading', url) return subprocess.check_output(['curl.exe', '-fSL', url]) except subprocess.CalledProcessError: time.sleep(1) raise SystemExit('Failed to download: {}'.format(url)) def sw(): sw = os.environ['SW'] os.makedirs(sw) os.chdir(sw) url = 'https://download.calibre-ebook.com/travis/win-{}.tar.xz'.format( '64' if is64bit else '32') tarball = download_file(url) with tarfile.open(fileobj=io.BytesIO(tarball)) as tf: tf.extractall() printf('Download complete') def sanitize_path(): needed_paths = [] executables = 'git.exe curl.exe rapydscript.cmd node.exe'.split() for p in os.environ['PATH'].split(os.pathsep): for x in tuple(executables): if os.path.exists(os.path.join(p, x)): needed_paths.append(p) executables.remove(x) sw = os.environ['SW'] paths = r'{0}\private\python\DLLs {0}\private\python\Lib\site-packages\pywin32_system32 {0}\bin {0}\qt\bin C:\Windows\System32'.format( sw ).split() + needed_paths os.environ['PATH'] = os.pathsep.join(paths) print('PATH:', os.environ['PATH']) def vcenv(): env = os.environ.copy() env.update(vcvars()) return {str(k): str(v) for k, v in env.items()} def build(): sanitize_path() cmd = [sys.executable, 'setup.py', 'bootstrap', '--ephemeral'] printf(*cmd) p = subprocess.Popen(cmd, env=vcenv()) raise SystemExit(p.wait()) def test(): sanitize_path() cmd = [sys.executable, 'setup.py', 'test'] printf(*cmd) p = subprocess.Popen(cmd) raise SystemExit(p.wait()) def main(): q = sys.argv[-1] if q == 'build': build() elif q == 'test': test() elif q == 'sw': sw() else: if len(sys.argv) == 1: raise SystemExit('Usage: win-ci.py sw|build|test') raise SystemExit('%r is not a valid action' % sys.argv[-1]) if __name__ == '__main__': main()