diff --git a/.github/workflows/win-ci.py b/.github/workflows/win-ci.py index c4696f1..1df2fea 100644 --- a/.github/workflows/win-ci.py +++ b/.github/workflows/win-ci.py @@ -2,18 +2,19 @@ # vim:fileencoding=utf-8 # License: Apache 2.0 Copyright: 2017, Kovid Goyal -from __future__ import print_function - +import ctypes.wintypes import errno import glob import io import os +import re import shlex import shutil import subprocess import sys import tarfile import time +from functools import lru_cache ZLIB = "http://zlib.net/zlib-{}.tar.xz".format("1.3") LIBXML2 = "https://gitlab.gnome.org/GNOME/libxml2/-/archive/v2.12.0/libxml2-v2.12.0.tar.gz" @@ -81,11 +82,6 @@ def run(*args, env=None, cwd=None): raise SystemExit(p.returncode) -def distutils_vcvars(): - from setuptools._distutils.msvc9compiler import find_vcvarsall, get_build_version - return find_vcvarsall(get_build_version()) - - def remove_dups(variable): old_list = variable.split(os.pathsep) new_list = [] @@ -95,45 +91,6 @@ def remove_dups(variable): return os.pathsep.join(new_list) -def query_process(cmd): - if plat == 'amd64' and 'PROGRAMFILES(x86)' not in os.environ: - os.environ['PROGRAMFILES(x86)'] = os.environ['PROGRAMFILES'] + ' (x86)' - result = {} - try: - popen = subprocess.Popen(cmd, stdout=subprocess.PIPE, - stderr=subprocess.PIPE, shell=True) - except FileNotFoundError as err: - raise SystemExit(f'Failed to execute: {cmd!r} with error: {err}') - 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(): - vcvarsall = distutils_vcvars() - print('Querying vcvars:', vcvarsall) - return query_process('"%s" %s & set' % (vcvarsall, plat)) - - def download_and_extract(url): raw = io.BytesIO(download_file(url)) with tarfile.open(fileobj=raw, mode='r:*') as f: @@ -264,8 +221,141 @@ def lxml(): os.rename(package, os.path.join(SW, 'python', 'lib', 'site-packages', 'lxml')) +CSIDL_PROGRAM_FILES = 38 +CSIDL_PROGRAM_FILESX86 = 42 + + +@lru_cache() +def get_program_files_location(which=CSIDL_PROGRAM_FILESX86): + SHGFP_TYPE_CURRENT = 0 + buf = ctypes.create_unicode_buffer(ctypes.wintypes.MAX_PATH) + ctypes.windll.shell32.SHGetFolderPathW( + 0, which, 0, SHGFP_TYPE_CURRENT, buf) + return buf.value + + +@lru_cache() +def find_vswhere(): + for which in (CSIDL_PROGRAM_FILESX86, CSIDL_PROGRAM_FILES): + root = get_program_files_location(which) + vswhere = os.path.join(root, "Microsoft Visual Studio", "Installer", + "vswhere.exe") + if os.path.exists(vswhere): + return vswhere + raise SystemExit('Could not find vswhere.exe') + + +def get_output(*cmd): + return subprocess.check_output(cmd, encoding='mbcs', errors='strict') + + +@lru_cache() +def find_visual_studio(): + path = get_output( + find_vswhere(), + "-latest", + "-requires", + "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", + "-property", + "installationPath", + "-products", + "*" + ).strip() + return os.path.join(path, "VC", "Auxiliary", "Build") + + +@lru_cache() +def find_msbuild(): + base_path = get_output( + find_vswhere(), + "-latest", + "-requires", "Microsoft.Component.MSBuild", + "-property", 'installationPath' + ).strip() + return glob(os.path.join( + base_path, 'MSBuild', '*', 'Bin', 'MSBuild.exe'))[0] + + +def find_vcvarsall(): + productdir = find_visual_studio() + 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 query_process(cmd, is64bit): + if is64bit and 'PROGRAMFILES(x86)' not in os.environ: + os.environ['PROGRAMFILES(x86)'] = get_program_files_location() + 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 + + +@lru_cache() +def query_vcvarsall(is64bit=True): + plat = 'amd64' if is64bit else 'amd64_x86' + vcvarsall = find_vcvarsall() + env = query_process(f'"{vcvarsall}" {plat} & set', is64bit) + pat = re.compile('vs(\d+)comntools', re.I) + + comn_tools = {} + + for k in env: + m = pat.match(k) + if m is not None: + comn_tools[k] = int(m.group(1)) + comntools = sorted(comn_tools, key=comn_tools.__getitem__)[-1] + + + def g(k): + try: + return env[k] + except KeyError: + try: + return env[k.lower()] + except KeyError: + for k, v in env.items(): + print(f'{k}={v}', file=sys.stderr) + raise + + return { + k: g(k) + for k in ( + 'PATH LIB INCLUDE LIBPATH WINDOWSSDKDIR' + f' {comntools} PLATFORM' + ' UCRTVERSION UNIVERSALCRTSDKDIR VCTOOLSVERSION WINDOWSSDKDIR' + ' WINDOWSSDKVERSION WINDOWSSDKVERBINPATH WINDOWSSDKBINPATH' + ' VISUALSTUDIOVERSION VSCMD_ARG_HOST_ARCH VSCMD_ARG_TGT_ARCH' + ).split() + } + + def install_deps(): - run(PYTHON, '-m', 'pip', 'install', 'setuptools') env = query_vcvarsall() os.environ.update(env) print(PYTHON)