diff --git a/setup/build.py b/setup/build.py index 2e0580f37a..08563865c4 100644 --- a/setup/build.py +++ b/setup/build.py @@ -166,7 +166,7 @@ def read_extensions(): def init_env(): - from setup.build_environment import msvc, is64bit, win_inc, win_lib, NMAKE + from setup.build_environment import win_ld, is64bit, win_inc, win_lib, NMAKE, win_cc from distutils import sysconfig linker = None if isunix: @@ -211,7 +211,7 @@ def init_env(): cflags.append('-I'+sysconfig.get_python_inc()) if iswindows: - cc = cxx = msvc.cc + cc = cxx = win_cc cflags = '/c /nologo /MD /W3 /EHsc /utf-8 /DNDEBUG'.split() ldflags = '/DLL /nologo /INCREMENTAL:NO /NODEFAULTLIB:libcmt.lib'.split() # cflags = '/c /nologo /Ox /MD /W3 /EHsc /Zi'.split() @@ -226,7 +226,7 @@ def init_env(): ldflags.append('/LIBPATH:'+p) cflags.append('-I%s'%sysconfig.get_python_inc()) ldflags.append('/LIBPATH:'+os.path.join(sysconfig.PREFIX, 'libs')) - linker = msvc.linker + linker = win_ld return namedtuple('Environment', 'cc cxx cflags ldflags linker make')( cc=cc, cxx=cxx, cflags=cflags, ldflags=ldflags, linker=linker, make=NMAKE if iswindows else 'make') diff --git a/setup/build_environment.py b/setup/build_environment.py index c47609b46c..be7283d80d 100644 --- a/setup/build_environment.py +++ b/setup/build_environment.py @@ -6,22 +6,22 @@ __license__ = 'GPL v3' __copyright__ = '2009, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import os, subprocess, re +import os, subprocess, re, shutil from distutils.spawn import find_executable from setup import ismacos, iswindows, is64bit, islinux, ishaiku -is64bit -NMAKE = RC = msvc = MT = win_inc = win_lib = None +NMAKE = RC = msvc = MT = win_inc = win_lib = win_cc = win_ld = None if iswindows: - from distutils import msvc9compiler - msvc = msvc9compiler.MSVCCompiler() - msvc.initialize() - NMAKE = msvc.find_exe('nmake.exe') - RC = msvc.find_exe('rc.exe') - MT = msvc.find_exe('mt.exe') - win_inc = [x for x in os.environ['include'].split(';') if x] - win_lib = [x for x in os.environ['lib'].split(';') if x] + from setup.vcvars import query_vcvarsall + env = query_vcvarsall(is64bit) + NMAKE = shutil.which('nmake.exe', path=env['PATH']) + RC = shutil.which('rc.exe', path=env['PATH']) + MT = shutil.which('mt.exe', path=env['PATH']) + win_cc = shutil.which('cl.exe', path=env['PATH']) + win_ld = shutil.which('link.exe', path=env['PATH']) + win_inc = [x for x in env['INCLUDE'].split(';') if x] + win_lib = [x for x in env['LIB'].split(';') if x] QMAKE = 'qmake' for x in ('qmake-qt5', 'qt5-qmake', 'qmake'): diff --git a/setup/vcvars.py b/setup/vcvars.py index 1409861c27..90f2c32e2d 100644 --- a/setup/vcvars.py +++ b/setup/vcvars.py @@ -1,18 +1,89 @@ #!/usr/bin/env python -# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai +# vim:fileencoding=utf-8 +# License: GPLv3 Copyright: 2016, Kovid Goyal + +import ctypes.wintypes +import os +import re +import subprocess +import sys +from functools import lru_cache +from glob import glob + +# See the table at https://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B#Internal_version_numbering +python_msc_version = int(re.search(r'\[MSC v\.(\d+) ', sys.version).group(1)) +if python_msc_version < 1920: + raise SystemExit(f'Python MSC version {python_msc_version} too old, needs Visual studio 2019') +if python_msc_version > 1929: + raise SystemExit(f'Python MSC version {python_msc_version} too new, needs Visual studio 2019') + +# The values are for VisualStudio 2019 (python_msc_version 192_) +VS_VERSION = '16.0' +COMN_TOOLS_VERSION = '160' + +CSIDL_PROGRAM_FILES = 38 +CSIDL_PROGRAM_FILESX86 = 42 -__license__ = 'GPL v3' -__copyright__ = '2012, Kovid Goyal ' -__docformat__ = 'restructuredtext en' +@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 -import os, sys, subprocess -plat = 'amd64' if sys.maxsize > 2**32 else 'x86' +@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(version=VS_VERSION): + path = get_output( + find_vswhere(), + "-version", version, + "-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(version=VS_VERSION): + base_path = get_output( + find_vswhere(), + "-version", version, + "-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 distutils_vcvars(): - from distutils.msvc9compiler import find_vcvarsall, get_build_version - return find_vcvarsall(get_build_version()) def remove_dups(variable): old_list = variable.split(os.pathsep) @@ -22,11 +93,13 @@ def remove_dups(variable): new_list.append(i) 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)' + +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, + popen = subprocess.Popen(cmd, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) try: stdout, stderr = popen.communicate() @@ -51,38 +124,26 @@ def query_process(cmd): popen.stderr.close() return result -def query_vcvarsall(): - vcvarsall = distutils_vcvars() - return query_process('"%s" %s & set' % (vcvarsall, plat)) -env = query_vcvarsall() -paths = env['path'].split(';') -lib = env['lib'] -include = env['include'] -libpath = env['libpath'] -sdkdir = env['windowssdkdir'] +@lru_cache() +def query_vcvarsall(is64bit=True): + plat = 'amd64' if is64bit else 'amd64_x86' + vcvarsall = find_vcvarsall() + env = query_process('"%s" %s & set' % (vcvarsall, plat), is64bit) -def unix(paths): - up = [] - for p in paths: - prefix, p = p.replace(os.sep, '/').partition('/')[0::2] - up.append('/cygdrive/%s/%s'%(prefix[0].lower(), p)) - return ':'.join(up) - -raw = '''\ -#!/bin/sh - -export PATH="%s:$PATH" - -export LIB="%s" - -export INCLUDE="%s" - -export LIBPATH="%s" - -export WindowsSdkDir="%s" - -'''%(unix(paths), lib.replace('\\', r'\\'), include.replace('\\', r'\\'), libpath.replace('\\', r'\\'), sdkdir.replace('\\', r'\\')) - -print(raw.encode('utf-8')) + def g(k): + try: + return env[k] + except KeyError: + return env[k.lower()] + return { + k: g(k) + for k in ( + 'PATH LIB INCLUDE LIBPATH WINDOWSSDKDIR' + f' VS{COMN_TOOLS_VERSION}COMNTOOLS PLATFORM' + ' UCRTVERSION UNIVERSALCRTSDKDIR VCTOOLSVERSION WINDOWSSDKDIR' + ' WINDOWSSDKVERSION WINDOWSSDKVERBINPATH WINDOWSSDKBINPATH' + ' VISUALSTUDIOVERSION VSCMD_ARG_HOST_ARCH VSCMD_ARG_TGT_ARCH' + ).split() + }