如果你在windows平台通过pip从pypi下载包,该模块包含需要使用msvc编译的c++的源码,且源站不包含已编译的模块(例如安装的python版本太新导致获取某库时只能拉取到源码,需要自行构建)。这时就需要你安装msvc build tools
来配置当前windows平台的构建工具。如果没有安装构建工具,将会报类似于Unable to find vcvarsall.bat
的未查找到vcvarsall.bat
的错误。
vcvarsall.bat
file is a batch used to set up the environment for the Microsoft Vistual C++ compiler
此批处理文件用于配置msvc编译器环境,总之是包含c/c++源码的python包需要用到的。在这里我会简单分析python包打包分发工具setuptools
查找vcvarsall.bat
的过程。
setuptools仓库地址
我们先从报错开始,在仓库中搜索unable to find vcvarsall.bat
,发现存在多个结果,
setuptools/_distutils/msvc9compiler.py setuptools/_distutils/_msvccompiler.py setuptools/msvc.py
第一个适用于msvc9,由于我使用的为vs2022,配套msvc版本为143,我们直接跳过。 第二三个源码在查找过程中的逻辑甚至语句高度相似,且msvc.py
中对应的*_find_vc*
注释均有"""Python 3.8 "distutils/_msvccompiler.py" backport"""
,总之是和补丁有关。总之我们选择第三个源码文件的find函数进行分析。setuptools/msvc.py#L165 这行rasie的exception的提示就是Unable to find vcvarsall.bat
。我们向上查找,
1 2 3 4 5 vcvarsall, vcruntime = _msvc14_find_vcvarsall(plat_spec)if not vcvarsall: raise distutils.errors.DistutilsPlatformError( "Unable to find vcvarsall.bat" )
发现调用了 _msvc14_find_vcvarsall
函数。它位于setuptools/msvc.py#L115
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 def _msvc14_find_vcvarsall (plat_spec ): """Python 3.8 "distutils/_msvccompiler.py" backport""" _, best_dir = _msvc14_find_vc2017() vcruntime = None if plat_spec in PLAT_SPEC_TO_RUNTIME: vcruntime_plat = PLAT_SPEC_TO_RUNTIME[plat_spec] else : vcruntime_plat = 'x64' if 'amd64' in plat_spec else 'x86' if best_dir: vcredist = join(best_dir, ".." , ".." , "redist" , "MSVC" , "**" , vcruntime_plat, "Microsoft.VC14*.CRT" , "vcruntime140.dll" ) try : import glob vcruntime = glob.glob(vcredist, recursive=True )[-1 ] except (ImportError, OSError, LookupError): vcruntime = None if not best_dir: best_version, best_dir = _msvc14_find_vc2015() if best_version: vcruntime = join(best_dir, 'redist' , vcruntime_plat, "Microsoft.VC140.CRT" , "vcruntime140.dll" ) if not best_dir: return None , None vcvarsall = join(best_dir, "vcvarsall.bat" ) if not isfile(vcvarsall): return None , None if not vcruntime or not isfile(vcruntime): vcruntime = None return vcvarsall, vcruntime
其中调用了_msvc14_find_vc2017
setuptools/msvc.py#L70
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 def _msvc14_find_vc2017 (): """Python 3.8 "distutils/_msvccompiler.py" backport Returns "15, path" based on the result of invoking vswhere.exe If no install is found, returns "None, None" The version is returned to avoid unnecessarily changing the function result. It may be ignored when the path is not None. If vswhere.exe is not available, by definition, VS 2017 is not installed. """ root = environ.get("ProgramFiles(x86)" ) or environ.get("ProgramFiles" ) if not root: return None , None try : path = subprocess.check_output([ join(root, "Microsoft Visual Studio" , "Installer" , "vswhere.exe" ), "-latest" , "-prerelease" , "-requiresAny" , "-requires" , "Microsoft.VisualStudio.Component.VC.Tools.x86.x64" , "-requires" , "Microsoft.VisualStudio.Workload.WDExpress" , "-property" , "installationPath" , "-products" , "*" , ]).decode(encoding="mbcs" , errors="strict" ).strip() except (subprocess.CalledProcessError, OSError, UnicodeDecodeError): return None , None path = join(path, "VC" , "Auxiliary" , "Build" ) if isdir(path): return 15 , path return None , None
我们掐头去尾从_msvc14_find_vc2017
开始分析获取vcvarsall.bat的过程
从环境变量中取得Program Files (x86)
或者Program Files
的路径
拼接路径得到vs installer 根目录下的vswhere.exe
程序
开启子进程,执行vswhere.exe -latest -prerelease -requiresAny -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -requires Microsoft.VisualStudio.Workload.WDExpress -property installationPath -products *
指令,通过输出获取vs根目录的路径。
拼接vc路径(path变量)为vs根目录\VC\Auxiliary\Build
此路径被返回到_msvc14_find_vcvarsall
函数:
msvc根目录路径返回给名为best_dir变量
如果根目录路径不存在就调用_msvc_find_vs2015
,再不存在就返回None
把best_dir拼接为best_dir/../../redist/MSVC/**/[PLAT_SPEC_TO_RUNTIME]/Microsoft.VC14*.CRT/vcruntime140.dll
,其中PLAT_SPEC_TORUNTIME是一个dict,其中存储各架构对应的文件夹名
1 2 3 4 5 6 PLAT_SPEC_TO_RUNTIME = { 'x86' : 'x86' , 'x86_amd64' : 'x64' , 'x86_arm' : 'arm' , 'x86_arm64' : 'arm64' }
使用glob库匹配到对应文件,生成包含vcruntime.dll
的vcredist
变量。 8. 通过拼接[best_dir]/vcvarsall.bat
生成vcvarsall
变量,最后返回vcvarsall
和vcruntime
变量
进入_msvc14_get_vc_env函数
以上过程之后的过程简单来说就是如果能查找到vcvarsall.bat就继续往下运行。否则就raise一个错误报错无法找到vcvarsall.bat。
至此我简单分析了它查找vcvarsall.bat的过程,可能没有从头分析,但是总归有所收获,例如可以通过vswhere.exe程序查找vs的根目录,以及glot模块的用处。