"""
Support code for building Python extensions on Windows.
# NT stuff
# 1. Make sure libpython<version>.a exists for gcc. If not, build it.
# 2. Force windows to use gcc (we're struggling with MSVC and g77 support)
# 3. Force windows to use g77
"""# 导入必要的库import os # 导入操作系统功能模块import sys # 导入系统相关功能模块import subprocess # 导入子进程管理模块,用于执行外部命令import re # 导入正则表达式模块import textwrap # 导入文本包装模块# Overwrite certain distutils.ccompiler functions:import numpy.distutils.ccompiler # noqa: F401 # 导入 NumPy 的 C 编译器相关功能模块,忽略 F401 警告from numpy.distutils import log # 导入 NumPy 日志功能模块# NT stuff# 1. Make sure libpython<version>.a exists for gcc. If not, build it.# 2. Force windows to use gcc (we're struggling with MSVC and g77 support)# --> this is done in numpy/distutils/ccompiler.py# 3. Force windows to use g77import distutils.cygwinccompiler # 导入 distutils Cygwin C 编译器模块from distutils.unixccompiler import UnixCCompiler # 导入 distutils Unix C 编译器类from distutils.msvccompiler import get_build_version as get_build_msvc_version # 导入 MSVC 编译器相关版本检查函数from distutils.errors import UnknownFileError # 导入未知文件错误类from numpy.distutils.misc_util import(msvc_runtime_library,# 导入 NumPy 的编译运行时库相关函数
msvc_runtime_version,
msvc_runtime_major,
get_build_architecture)defget_msvcr_replacement():"""Replacement for outdated version of get_msvcr from cygwinccompiler"""
msvcr = msvc_runtime_library()return[]if msvcr isNoneelse[msvcr]# Useful to generate table of symbols from a dll
_START = re.compile(r'\[Ordinal/Name Pointer\] Table')# 编译正则表达式,用于匹配符号表的起始部分
_TABLE = re.compile(r'^\s+\[([\s*[0-9]*)\] ([a-zA-Z0-9_]*)')# 编译正则表达式,用于匹配表格中的符号条目# the same as cygwin plus some additional parametersclassMingw32CCompiler(distutils.cygwinccompiler.CygwinCCompiler):""" A modified MingW32 compiler compatible with an MSVC built Python.
"""
compiler_type ='mingw32'# 设置编译器类型为 'mingw32'def__init__(self,
verbose=0,
dry_run=0,
force=0):# 调用父类构造函数初始化基类的属性
distutils.cygwinccompiler.CygwinCCompiler.__init__ (self, verbose,
dry_run, force)# **changes: eric jones 4/11/01# 1. 检查在 Windows 上是否存在导入库文件。如果不存在则构建它。
build_import_library()# 检查是否存在自定义的 MSVC 运行时库文件。如果不存在则构建它。
msvcr_success = build_msvcr_library()
msvcr_dbg_success = build_msvcr_library(debug=True)if msvcr_success or msvcr_dbg_success:# 添加预处理语句以使用自定义的 MSVC 运行时库
self.define_macro('NPY_MINGW_USE_CUSTOM_MSVCR')# 为 MinGW 定义 MSVC 版本信息
msvcr_version = msvc_runtime_version()if msvcr_version:
self.define_macro('__MSVCRT_VERSION__','0x%04i'% msvcr_version)# 当在 Windows 下为 amd64 架构构建时,应定义 MS_WIN64# Python 头文件只为 MS 编译器定义了 MS_WIN64,而这会导致一些问题,# 如使用 Py_ModuleInit4 而不是 Py_ModuleInit4_64 等,因此我们在这里添加它if get_build_architecture()=='AMD64':
self.set_executables(
compiler='gcc -g -DDEBUG -DMS_WIN64 -O0 -Wall',
compiler_so='gcc -g -DDEBUG -DMS_WIN64 -O0 -Wall ''-Wstrict-prototypes',
linker_exe='gcc -g',
linker_so='gcc -g -shared')else:
self.set_executables(
compiler='gcc -O2 -Wall',
compiler_so='gcc -O2 -Wall -Wstrict-prototypes',
linker_exe='g++ ',
linker_so='g++ -shared')# 为了支持 Python 2.3,我们单独设置 self.compiler_cxx# 因为在 2.2 之前的版本无法通过 set_executables 传递该参数
self.compiler_cxx =['g++']# 可能我们还应该添加 -mthreads,但这会导致生成的 DLL 需要另一个 DLL(mingwm10.dll 参见 Mingw32 文档)# (-mthreads: 在 Mingw32 上支持线程安全的异常处理)# 没有额外的库需要链接#self.dll_libraries=[]returndeflink(self,
target_desc,
objects,
output_filename,
output_dir,
libraries,
library_dirs,
runtime_library_dirs,
export_symbols =None,
debug=0,
extra_preargs=None,
extra_postargs=None,
build_temp=None,
target_lang=None):# 根据 Python 使用的编译器确定要包含的 MSVC 运行时库
runtime_library = msvc_runtime_library()if runtime_library:# 如果没有传入库列表,则初始化为空列表ifnot libraries:
libraries =[]# 将确定的运行时库添加到库列表中
libraries.append(runtime_library)# 准备函数调用所需的参数元组
args =(self,
target_desc,
objects,
output_filename,
output_dir,
libraries,
library_dirs,
runtime_library_dirs,None,#export_symbols, 我们在定义文件中完成这一步骤
debug,
extra_preargs,
extra_postargs,
build_temp,
target_lang)# 调用 UnixCCompiler 类的 link 方法来进行链接操作
func = UnixCCompiler.link
func(*args[:func.__code__.co_argcount])# 函数无返回值,直接结束returndefobject_filenames(self,
source_filenames,
strip_dir=0,
output_dir=''):# 如果未指定输出目录,则将其设为空字符串if output_dir isNone: output_dir =''# 初始化目标文件名列表为空
obj_names =[]# 遍历源文件名列表for src_name in source_filenames:# 使用 normcase 确保文件扩展名是正确的大小写(base, ext)= os.path.splitext (os.path.normcase(src_name))# 添加以下代码以去除 Windows 驱动器信息# 如果不这样做,.o 文件会被放在与 .c 文件相同的位置,而不是构建目录中
drv, base = os.path.splitdrive(base)if drv:
base = base[1:]# 如果文件扩展名不在源文件扩展名列表中,抛出未知文件类型异常if ext notin(self.src_extensions +['.rc','.res']):raise UnknownFileError("unknown file type '%s' (from '%s')"% \
(ext, src_name))# 如果要去除目录信息,则获取文件的基本名称if strip_dir:
base = os.path.basename (base)# 如果文件扩展名是 .res 或者 .rc,将其编译成目标文件if ext =='.res'or ext =='.rc':
obj_names.append (os.path.join (output_dir,
base + ext + self.obj_extension))else:# 否则直接生成目标文件名并加入列表
obj_names.append (os.path.join (output_dir,
base + self.obj_extension))# 返回生成的目标文件名列表return obj_names
deffind_python_dll():# We can't do much here:# - find it in the virtualenv (sys.prefix)# - find it in python main dir (sys.base_prefix, if in a virtualenv)# - in system32,# - otherwise (Sxs), I don't know how to get it.
stems =[sys.prefix]if sys.base_prefix != sys.prefix:
stems.append(sys.base_prefix)
sub_dirs =['','lib','bin']# generate possible combinations of directory trees and sub-directories
lib_dirs =[]for stem in stems:for folder in sub_dirs:
lib_dirs.append(os.path.join(stem, folder))# add system directory as wellif'SYSTEMROOT'in os.environ:
lib_dirs.append(os.path.join(os.environ['SYSTEMROOT'],'System32'))# determine the Python DLL filename based on version and implementation
major_version, minor_version =tuple(sys.version_info[:2])
implementation = sys.implementation.name
if implementation =='cpython':
dllname =f'python{major_version}{minor_version}.dll'elif implementation =='pypy':
dllname =f'libpypy{major_version}.{minor_version}-c.dll'else:
dllname =f'Unknown platform {implementation}'print("Looking for %s"% dllname)# search through the generated library directories for the Python DLLfor folder in lib_dirs:
dll = os.path.join(folder, dllname)if os.path.exists(dll):return dll
# if the DLL is not found, raise an errorraise ValueError("%s not found in %s"%(dllname, lib_dirs))defdump_table(dll):# use objdump to get the symbol table of the given DLL
st = subprocess.check_output(["objdump.exe","-p", dll])return st.split(b'\n')defgenerate_def(dll, dfile):"""
Given a dll file location, get all its exported symbols and dump them
into the given def file.
The .def file will be overwritten
"""# dump the symbol table from the DLL
dump = dump_table(dll)# find the start of the symbol tablefor i inrange(len(dump)):if _START.match(dump[i].decode()):breakelse:raise ValueError("Symbol table not found")# extract symbols from the symbol table
syms =[]for j inrange(i +1,len(dump)):
m = _TABLE.match(dump[j].decode())if m:
syms.append((int(m.group(1).strip()), m.group(2)))else:break# if no symbols are found, issue a warningiflen(syms)==0:
log.warn('No symbols found in %s'% dll)# write the symbols into the .def filewithopen(dfile,'w')as d:
d.write('LIBRARY %s\n'% os.path.basename(dll))
d.write(';CODE PRELOAD MOVEABLE DISCARDABLE\n')
d.write(';DATA PRELOAD SINGLE\n')
d.write('\nEXPORTS\n')for s in syms:
d.write('%s\n'% s[1])deffind_dll(dll_name):# determine the architecture and return the appropriate architecture string
arch ={'AMD64':'amd64','Intel':'x86'}[get_build_architecture()]# 在 WinSxS 目录中查找指定的 DLL 文件def_find_dll_in_winsxs(dll_name):# 获取系统的 Windows 目录(默认为 C:\WINDOWS)并拼接 WinSxS 目录路径
winsxs_path = os.path.join(os.environ.get('WINDIR',r'C:\WINDOWS'),'winsxs')# 如果 WinSxS 目录不存在,则返回 Noneifnot os.path.exists(winsxs_path):returnNone# 遍历 WinSxS 目录及其子目录for root, dirs, files in os.walk(winsxs_path):# 如果找到目标 DLL 文件且包含当前系统架构信息在路径中,则返回完整文件路径if dll_name in files and arch in root:return os.path.join(root, dll_name)# 如果未找到目标 DLL 文件,则返回 NonereturnNone# 在 Python 安装目录及系统 PATH 中查找指定的 DLL 文件def_find_dll_in_path(dll_name):# 首先在 Python 安装目录下查找for path in[sys.prefix]+ os.environ['PATH'].split(';'):# 拼接文件路径
filepath = os.path.join(path, dll_name)# 如果找到文件,则返回其绝对路径if os.path.exists(filepath):return os.path.abspath(filepath)# 返回在 WinSxS 目录或系统 PATH 中找到的 DLL 文件的绝对路径return _find_dll_in_winsxs(dll_name)or _find_dll_in_path(dll_name)# 构建 MSVCR(Microsoft Visual C++ Runtime Library)库文件defbuild_msvcr_library(debug=False):# 如果操作系统不是 Windows,则返回 Falseif os.name !='nt':returnFalse# 获取 MSVC runtime 的版本号
msvcr_ver = msvc_runtime_major()# 如果版本号为 None,说明未找到 MSVC runtime,返回 Falseif msvcr_ver isNone:
log.debug('Skip building import library: Runtime is not compiled with MSVC')returnFalse# 跳过版本小于 MSVC 8.0 的自定义库if msvcr_ver <80:
log.debug('Skip building msvcr library: custom functionality not present')returnFalse# 获取 MSVC runtime 的库名称
msvcr_name = msvc_runtime_library()# 如果 debug 为 True,则在库名称后添加 'd'if debug:
msvcr_name +='d'# 如果自定义库已经存在,直接返回 True
out_name ="lib%s.a"% msvcr_name
out_file = os.path.join(sys.prefix,'libs', out_name)if os.path.isfile(out_file):
log.debug('Skip building msvcr library: "%s" exists'%(out_file,))returnTrue# 查找 msvcr.dll 文件
msvcr_dll_name = msvcr_name +'.dll'
dll_file = find_dll(msvcr_dll_name)# 如果未找到 msvcr.dll 文件,返回 Falseifnot dll_file:
log.warn('Cannot build msvcr library: "%s" not found'% msvcr_dll_name)returnFalse# 生成 msvcr 库的符号定义文件
def_name ="lib%s.def"% msvcr_name
def_file = os.path.join(sys.prefix,'libs', def_name)
log.info('Building msvcr library: "%s" (from %s)'%(out_file, dll_file))
generate_def(dll_file, def_file)# 使用符号定义文件创建自定义 mingw 库
cmd =['dlltool','-d', def_file,'-l', out_file]
retcode = subprocess.call(cmd)# 清理符号定义文件
os.remove(def_file)return(not retcode)# 构建 Python 运行时的导入库defbuild_import_library():# 如果操作系统不是 Windows,则直接返回if os.name !='nt':return# 获取构建体系结构
arch = get_build_architecture()# 根据不同的体系结构调用对应的函数进行构建if arch =='AMD64':return _build_import_library_amd64()elif arch =='Intel':return _build_import_library_x86()else:raise ValueError("Unhandled arch %s"% arch)# 检查 Python 运行时的导入库是否已存在def_check_for_import_lib():# 获取 Python 主版本号和次版本号
major_version, minor_version =tuple(sys.version_info[:2])# 导入库文件名的模式
patterns =['libpython%d%d.a','libpython%d%d.dll.a','libpython%d.%d.dll.a']# 可能包含导入库的目录树
stems =[sys.prefix]ifhasattr(sys,'base_prefix')and sys.base_prefix != sys.prefix:
stems.append(sys.base_prefix)elifhasattr(sys,'real_prefix')and sys.real_prefix != sys.prefix:
stems.append(sys.real_prefix)
sub_dirs =['libs','lib']# 生成候选位置列表
candidates =[]# 遍历模式列表for pat in patterns:# 根据当前主版本号和次版本号生成文件名
filename = pat %(major_version, minor_version)# 遍历根目录列表for stem_dir in stems:# 遍历子目录列表for folder in sub_dirs:# 构建候选文件路径并添加到候选位置列表中
candidates.append(os.path.join(stem_dir, folder, filename))# 检查文件系统以查找是否存在任何候选文件for fullname in candidates:if os.path.isfile(fullname):# 如果文件已存在于指定位置return(True, fullname)# 需要构建文件,首选位置放在候选位置列表的第一个return(False, candidates[0])def_build_import_library_amd64():
out_exists, out_file = _check_for_import_lib()if out_exists:
log.debug('Skip building import library: "%s" exists', out_file)return# 获取当前正在构建导入库的运行时 DLL
dll_file = find_python_dll()# 记录日志,指示正在构建导入库 (arch=AMD64),显示相关的文件信息
log.info('Building import library (arch=AMD64): "%s" (from %s)'%(out_file, dll_file))# 从 DLL 文件生成符号列表
def_name ="python%d%d.def"%tuple(sys.version_info[:2])
def_file = os.path.join(sys.prefix,'libs', def_name)
generate_def(dll_file, def_file)# 使用符号列表生成导入库
cmd =['dlltool','-d', def_file,'-l', out_file]
subprocess.check_call(cmd)def_build_import_library_x86():""" Build the import libraries for Mingw32-gcc on Windows
"""
out_exists, out_file = _check_for_import_lib()if out_exists:
log.debug('Skip building import library: "%s" exists', out_file)return# 根据 Python 版本信息生成导入库文件名
lib_name ="python%d%d.lib"%tuple(sys.version_info[:2])
lib_file = os.path.join(sys.prefix,'libs', lib_name)ifnot os.path.isfile(lib_file):# 如果在虚拟环境中找不到库文件,尝试基本分发目录,并在那里找到使用# 对于 Python 2.7 的虚拟环境,基本目录是 real_prefix 而不是 base_prefixifhasattr(sys,'base_prefix'):
base_lib = os.path.join(sys.base_prefix,'libs', lib_name)elifhasattr(sys,'real_prefix'):
base_lib = os.path.join(sys.real_prefix,'libs', lib_name)else:
base_lib =''# os.path.isfile('') == Falseif os.path.isfile(base_lib):
lib_file = base_lib
else:
log.warn('Cannot build import library: "%s" not found', lib_file)return# 记录日志,指示正在构建导入库 (ARCH=x86),显示相关的文件信息
log.info('Building import library (ARCH=x86): "%s"', out_file)from numpy.distutils import lib2def
# 根据 Python 版本信息生成符号定义文件名
def_name ="python%d%d.def"%tuple(sys.version_info[:2])
def_file = os.path.join(sys.prefix,'libs', def_name)# 使用 lib2def 生成符号列表
nm_output = lib2def.getnm(
lib2def.DEFAULT_NM +[lib_file], shell=False)
dlist, flist = lib2def.parse_nm(nm_output)withopen(def_file,'w')as fid:# 将符号列表输出到定义文件中
lib2def.output_def(dlist, flist, lib2def.DEF_HEADER, fid)# 查找 Python DLL 的路径
dll_name = find_python_dll()# 使用 dlltool 创建导入库
cmd =["dlltool","--dllname", dll_name,"--def", def_file,"--output-lib", out_file]
status = subprocess.check_output(cmd)if status:
log.warn('Failed to build import library for gcc. Linking will fail.')return#=====================================# Dealing with Visual Studio MANIFESTS#=====================================# 用于处理 Visual Studio 的清单文件的函数。清单文件是在 Windows 上强制 DLL 版本的一种机制,# 与 distutils 的 MANIFEST 没有关系。清单文件是带有版本信息的 XML 文件,用于# the OS loader; they are necessary when linking against a DLL not in the# system path; in particular, official python 2.6 binary is built against the# MS runtime 9 (the one from VS 2008), which is not available on most windows# systems; python 2.6 installer does install it in the Win SxS (Side by side)# directory, but this requires the manifest for this to work. This is a big# mess, thanks MS for a wonderful system.# XXX: ideally, we should use exactly the same version as used by python. I# submitted a patch to get this version, but it was only included for python# 2.6.1 and above. So for versions below, we use a "best guess".
_MSVCRVER_TO_FULLVER ={}if sys.platform =='win32':try:import msvcrt
# I took one version in my SxS directory: no idea if it is the good# one, and we can't retrieve it from python
_MSVCRVER_TO_FULLVER['80']="8.0.50727.42"
_MSVCRVER_TO_FULLVER['90']="9.0.21022.8"# Value from msvcrt.CRT_ASSEMBLY_VERSION under Python 3.3.0# on Windows XP:
_MSVCRVER_TO_FULLVER['100']="10.0.30319.460"
crt_ver =getattr(msvcrt,'CRT_ASSEMBLY_VERSION',None)if crt_ver isnotNone:# Available at least back to Python 3.3
maj,min= re.match(r'(\d+)\.(\d)', crt_ver).groups()
_MSVCRVER_TO_FULLVER[maj +min]= crt_ver
del maj,mindel crt_ver
except ImportError:# If we are here, means python was not built with MSVC. Not sure what# to do in that case: manifest building will fail, but it should not be# used in that case anyway
log.warn('Cannot import msvcrt: using manifest will not be possible')defmsvc_manifest_xml(maj,min):"""Given a major and minor version of the MSVCR, returns the
corresponding XML file."""try:
fullver = _MSVCRVER_TO_FULLVER[str(maj *10+min)]except KeyError:raise ValueError("Version %d,%d of MSVCRT not supported yet"%(maj,min))fromNone# Don't be fooled, it looks like an XML, but it is not. In particular, it# should not have any space before starting, and its size should be# divisible by 4, most likely for alignment constraints when the xml is# embedded in the binary...# This template was copied directly from the python 2.6 binary (using# strings.exe from mingw on python.exe).# 定义一个包含XML内容的模板字符串
template = textwrap.dedent("""\
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="asInvoker" uiAccess="false"></requestedExecutionLevel>
</requestedPrivileges>
</security>
</trustInfo>
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="Microsoft.VC%(maj)d%(min)d.CRT" version="%(fullver)s" processorArchitecture="*" publicKeyToken="1fc8b3b9a1e18e3b"></assemblyIdentity>
</dependentAssembly>
</dependency>
</assembly>""")# 使用模板字符串格式化替换,返回最终结果字符串return template %{'fullver': fullver,'maj': maj,'min':min}# 返回用于生成嵌入为给定清单文件名的 res 文件的 rc 文件defmanifest_rc(name,type='dll'):"""Return the rc file used to generate the res file which will be embedded
as manifest for given manifest file name, of given type ('dll' or
'exe').
Parameters
----------
name : str
name of the manifest file to embed
type : str {'dll', 'exe'}
type of the binary which will embed the manifest
"""# 根据给定的类型 ('dll' or 'exe') 为给定的清单文件生成嵌入的 res 文件的 rc 文件iftype=='dll':
rctype =2eliftype=='exe':
rctype =1else:raise ValueError("Type %s not supported"%type)return"""\
#include "winuser.h"
%d RT_MANIFEST %s"""%(rctype, name)# 检查嵌入的 msvcr 是否与链接的 msvcr 版本匹配defcheck_embedded_msvcr_match_linked(msver):"""msver is the ms runtime version used for the MANIFEST."""# 检查链接和嵌入的 msvcr 主版本号是否相同
maj = msvc_runtime_major()if maj:ifnot maj ==int(msver):raise ValueError("Discrepancy between linked msvcr " \
"(%d) and the one about to be embedded " \
"(%d)"%(int(msver), maj))# 获取配置测试的名称(包括后缀)defconfigtest_name(config):
base = os.path.basename(config._gen_temp_sourcefile("yo",[],"c"))return os.path.splitext(base)[0]# 获取清单文件的名称defmanifest_name(config):# 获取配置测试的名称(包括后缀)
root = configtest_name(config)
exext = config.compiler.exe_extension
return root + exext +".manifest"# 获取 rc 文件的名称defrc_name(config):# 获取配置测试的名称(包括后缀)
root = configtest_name(config)return root +".rc"# 生成清单文件defgenerate_manifest(config):
msver = get_build_msvc_version()if msver isnotNone:if msver >=8:
check_embedded_msvcr_match_linked(msver)
ma_str, mi_str =str(msver).split('.')# 写入清单文件
manxml = msvc_manifest_xml(int(ma_str),int(mi_str))withopen(manifest_name(config),"w")as man:
config.temp_files.append(manifest_name(config))
man.write(manxml)
.\numpy\numpy\distutils\misc_util.py
# 导入标准库模块import os # 提供与操作系统交互的功能import re # 提供正则表达式操作import sys # 提供与 Python 解释器交互的功能import copy # 提供复制对象的功能import glob # 提供文件通配符匹配import atexit # 提供退出时执行函数的注册和调用import tempfile # 提供创建临时文件和目录的功能import subprocess # 提供创建和管理子进程的功能import shutil # 提供高级文件操作功能import multiprocessing # 提供多进程处理支持import textwrap # 提供文本包装和填充功能import importlib.util # 提供导入模块的工具from threading import local as tlocal # 提供线程本地存储功能from functools importreduce# 提供高阶函数操作import distutils # Python 的标准库中的工具模块from distutils.errors import DistutilsError # 引入 distutils 中的错误异常类# 线程本地存储,用于存储每个线程的临时目录,以确保每个线程只创建一个临时目录
_tdata = tlocal()# 存储所有创建的临时目录,以便在退出时删除
_tmpdirs =[]defclean_up_temporary_directory():"""
清理临时目录的函数,在程序退出时被注册调用
"""if _tmpdirs isnotNone:for d in _tmpdirs:try:
shutil.rmtree(d)# 尝试删除临时目录及其内容except OSError:pass
atexit.register(clean_up_temporary_directory)# 注册清理临时目录的函数,确保程序退出时执行# 声明 __all__ 列表,指定模块中可以被外部导入的符号
__all__ =['Configuration','get_numpy_include_dirs','default_config_dict','dict_append','appendpath','generate_config_py','get_cmd','allpath','get_mathlibs','terminal_has_colors','red_text','green_text','yellow_text','blue_text','cyan_text','cyg2win32','mingw32','all_strings','has_f_sources','has_cxx_sources','filter_sources','get_dependencies','is_local_src_dir','get_ext_source_files','get_script_files','get_lib_source_files','get_data_files','dot_join','get_frame','minrelpath','njoin','is_sequence','is_string','as_list','gpaths','get_language','get_build_architecture','get_info','get_pkg_info','get_num_build_jobs','sanitize_cxx_flags','exec_mod_from_location']classInstallableLib:"""
可安装库的容器类,用于存储安装库的信息
Parameters
----------
name : str
安装库的名称
build_info : dict
存储构建信息的字典
target_dir : str
指定安装库的绝对路径
See Also
--------
Configuration.add_installed_library
Notes
-----
这三个参数被存储为同名的属性。
"""def__init__(self, name, build_info, target_dir):
self.name = name # 设置名称属性
self.build_info = build_info # 设置构建信息属性
self.target_dir = target_dir # 设置目标目录属性defget_num_build_jobs():"""
获取由 setup.py 的 --parallel 命令行参数设置的并行构建作业数
如果未设置该命令,检查环境变量 NPY_NUM_BUILD_JOBS 的设置。如果未设置,返回系统的处理器数量,最大为 8(以防止过载)。
Returns
-------
out : int
可以运行的并行作业数
"""from numpy.distutils.core import get_distribution # 导入 numpy.distutils 中的 get_distribution 函数try:
cpu_count =len(os.sched_getaffinity(0))# 尝试获取当前进程可用的 CPU 数量except AttributeError:
cpu_count = multiprocessing.cpu_count()# 获取系统中的 CPU 核心数
cpu_count =min(cpu_count,8)# 将 CPU 核心数限制在最大值为 8
envjobs =int(os.environ.get("NPY_NUM_BUILD_JOBS", cpu_count))# 获取环境变量中设置的并行作业数,如果未设置则使用 cpu_count# 获取当前项目的发行信息
dist = get_distribution()# 如果发行信息为None,说明在配置阶段可能未定义,直接返回envjobsif dist isNone:return envjobs
# 获取三个构建命令对象中的并行属性,任意一个设置了并行作业数即可,选择最大的
cmdattr =(getattr(dist.get_command_obj('build'),'parallel',None),getattr(dist.get_command_obj('build_ext'),'parallel',None),getattr(dist.get_command_obj('build_clib'),'parallel',None))# 如果三个命令对象的并行属性都为None,则返回envjobsifall(x isNonefor x in cmdattr):return envjobs
else:# 返回三个命令对象中并行属性不为None的最大值returnmax(x for x in cmdattr if x isnotNone)# 引用警告模块import warnings
# 定义函数:将参数列表中的每个参数进行引号处理defquote_args(args):"""Quote list of arguments.
.. deprecated:: 1.22.
"""# 发出警告:'quote_args'已被弃用
warnings.warn('"quote_args" is deprecated.',
DeprecationWarning, stacklevel=2)# 将参数列表转换为列表形式
args =list(args)# 遍历参数列表,对包含空格但未被引号包围的参数进行引号处理for i inrange(len(args)):
a = args[i]if' 'in a and a[0]notin'"\'':
args[i]='"%s"'%(a)# 返回处理后的参数列表return args
# 定义函数:将'/-'分隔的路径名转换为操作系统的路径分隔符defallpath(name):"Convert a /-separated pathname to one using the OS's path separator."
split = name.split('/')return os.path.join(*split)# 定义函数:返回相对于父路径的路径defrel_path(path, parent_path):"""Return path relative to parent_path."""# 使用realpath避免符号链接目录的问题(参见gh-7707)
pd = os.path.realpath(os.path.abspath(parent_path))
apath = os.path.realpath(os.path.abspath(path))iflen(apath)<len(pd):return path
if apath == pd:return''if pd == apath[:len(pd)]:assert apath[len(pd)]in[os.sep],repr((path, apath[len(pd)]))
path = apath[len(pd)+1:]return path
# 定义函数:根据调用堆栈中的帧对象返回模块的路径defget_path_from_frame(frame, parent_path=None):"""Return path of the module given a frame object from the call stack.
Returned path is relative to parent_path when given,
otherwise it is absolute path.
"""# 尝试在帧中查找文件名try:
caller_file =eval('__file__', frame.f_globals, frame.f_locals)
d = os.path.dirname(os.path.abspath(caller_file))except NameError:# 如果__file__未定义,则尝试使用__name__
caller_name =eval('__name__', frame.f_globals, frame.f_locals)__import__(caller_name)
mod = sys.modules[caller_name]ifhasattr(mod,'__file__'):
d = os.path.dirname(os.path.abspath(mod.__file__))else:# 执行setup.py时,返回当前目录的绝对路径
d = os.path.abspath('.')# 如果指定了父路径,则返回相对于父路径的路径if parent_path isnotNone:
d = rel_path(d, parent_path)# 返回模块路径或者当前目录(如果未找到模块路径)return d or'.'# 定义函数:连接两个或多个路径名组件,解析'..'和'.',并使用操作系统的路径分隔符defnjoin(*path):"""Join two or more pathname components +
- convert a /-separated pathname to one using the OS's path separator.
- resolve `..` and `.` from path.
Either passing n arguments as in njoin('a','b'), or a sequence
of n names as in njoin(['a','b']) is handled, or a mixture of such arguments.
"""
paths =[]# 遍历传入的路径名组件for p in path:if is_sequence(p):# 如果是序列,则递归处理
paths.append(njoin(*p))else:assert is_string(p)
paths.append(p)
path = paths
# 如果路径名组件为空,则返回空字符串ifnot path:# njoin()
joined =''else:# 否则连接路径名组件,并返回连接后的路径# njoin('a', 'b')
joined = os.path.join(*path)# 检查操作系统路径分隔符是否为斜杠'/'if os.path.sep !='/':# 如果不是斜杠'/',则用操作系统的路径分隔符替换路径中的斜杠'/'
joined = joined.replace('/', os.path.sep)# 调用minrelpath函数计算路径的最短相对路径,并返回结果return minrelpath(joined)# 返回numpyconfig.h中MATHLIB行的内容defget_mathlibs(path=None):"""Return the MATHLIB line from numpyconfig.h
"""# 如果提供了路径,则使用给定路径下的_numpyconfig.h文件if path isnotNone:
config_file = os.path.join(path,'_numpyconfig.h')else:# 否则,在每个numpy包含目录中查找文件
dirs = get_numpy_include_dirs()for path in dirs:
fn = os.path.join(path,'_numpyconfig.h')# 找到文件后设置配置文件路径并退出循环if os.path.exists(fn):
config_file = fn
breakelse:# 如果在所有目录中都找不到文件,则引发异常raise DistutilsError('_numpyconfig.h not found in numpy include ''dirs %r'%(dirs,))# 打开配置文件并读取内容withopen(config_file)as fid:
mathlibs =[]
s ='#define MATHLIB'# 逐行读取文件内容for line in fid:# 如果行以指定的标识符开头,则提取并处理相应的数学库信息if line.startswith(s):
value = line[len(s):].strip()if value:
mathlibs.extend(value.split(','))# 返回解析得到的数学库信息列表return mathlibs
# 解析路径中的`..`和`.`,返回规范化后的路径defminrelpath(path):"""Resolve `..` and '.' from path.
"""# 如果路径不是字符串,则直接返回ifnot is_string(path):return path
# 如果路径中没有`.`,则直接返回if'.'notin path:return path
l = path.split(os.sep)while l:try:
i = l.index('.',1)except ValueError:breakdel l[i]
j =1while l:try:
i = l.index('..', j)except ValueError:breakif l[i-1]=='..':
j +=1else:del l[i], l[i-1]
j =1# 如果路径列表为空,则返回空字符串;否则返回重新连接后的路径ifnot l:return''return os.sep.join(l)# 对glob.glob返回的结果进行排序,以解决https://bugs.python.org/issue30461问题defsorted_glob(fileglob):"""sorts output of python glob for https://bugs.python.org/issue30461
to allow extensions to have reproducible build results"""# 对glob.glob的结果进行排序并返回returnsorted(glob.glob(fileglob))# 对路径列表进行修正,确保它是一个序列,并且不是字符串def_fix_paths(paths, local_path, include_non_existing):assert is_sequence(paths),repr(type(paths))
new_paths =[]# 断言路径不是字符串,避免意外的类型错误assertnot is_string(paths),repr(paths)# 遍历给定的路径列表 pathsfor n in paths:# 检查当前路径 n 是否为字符串if is_string(n):# 如果路径中包含通配符 '*' 或 '?',则使用 sorted_glob 函数获取匹配的路径列表 pif'*'in n or'?'in n:
p = sorted_glob(n)# 使用 njoin 函数将 local_path 和 n 进行拼接,再使用 sorted_glob 函数获取匹配的路径列表 p2
p2 = sorted_glob(njoin(local_path, n))# 如果 p2 列表非空,则将其添加到 new_paths 列表中if p2:
new_paths.extend(p2)# 否则,如果 p 列表非空,则将其添加到 new_paths 列表中elif p:
new_paths.extend(p)else:# 如果 include_non_existing 为 True,则将当前路径 n 添加到 new_paths 列表中if include_non_existing:
new_paths.append(n)# 打印未能解析匹配模式的信息print('could not resolve pattern in %r: %r'%(local_path, n))else:# 使用 njoin 函数将 local_path 和 n 进行拼接,得到完整路径 n2
n2 = njoin(local_path, n)# 如果 n2 存在于文件系统中,则将其添加到 new_paths 列表中if os.path.exists(n2):
new_paths.append(n2)else:# 否则,如果 n 存在于文件系统中,则将 n 添加到 new_paths 列表中if os.path.exists(n):
new_paths.append(n)# 如果 include_non_existing 为 True,则将 n 添加到 new_paths 列表中elif include_non_existing:
new_paths.append(n)# 如果 n 依然不存在,则打印不存在路径的信息ifnot os.path.exists(n):print('non-existing path in %r: %r'%(local_path, n))# 如果 n 是一个序列(如列表或元组),则递归调用 _fix_paths 函数处理其中的路径,并将结果扩展到 new_paths 列表中elif is_sequence(n):
new_paths.extend(_fix_paths(n, local_path, include_non_existing))else:# 如果 n 不是字符串也不是序列,则直接将其添加到 new_paths 列表中
new_paths.append(n)# 返回处理后的路径列表 new_paths,并对每个路径使用 minrelpath 函数进行最小化处理return[minrelpath(p)for p in new_paths]# 将路径列表应用 glob 函数,并根据需要添加本地路径defgpaths(paths, local_path='', include_non_existing=True):"""Apply glob to paths and prepend local_path if needed.
"""# 如果 paths 是字符串,则将其转换成元组if is_string(paths):
paths =(paths,)# 返回修正后的路径列表return _fix_paths(paths, local_path, include_non_existing)# 创建临时文件,返回文件对象和文件名defmake_temp_file(suffix='', prefix='', text=True):# 如果 _tdata 没有 tempdir 属性,则创建临时目录ifnothasattr(_tdata,'tempdir'):
_tdata.tempdir = tempfile.mkdtemp()
_tmpdirs.append(_tdata.tempdir)# 创建临时文件,返回文件对象和文件名
fid, name = tempfile.mkstemp(suffix=suffix,
prefix=prefix,dir=_tdata.tempdir,
text=text)
fo = os.fdopen(fid,'w')return fo, name
# 用于彩色终端输出的钩子defterminal_has_colors():# 如果是 cygwin 平台且未设置 USE_COLOR 环境变量,则返回 0if sys.platform=='cygwin'and'USE_COLOR'notin os.environ:return0# 如果标准输出是终端并且支持颜色ifhasattr(sys.stdout,'isatty')and sys.stdout.isatty():try:# 尝试导入 curses 模块import curses
curses.setupterm()# 如果终端支持颜色功能,则返回 1if(curses.tigetnum("colors")>=0and curses.tigetnum("pairs")>=0and((curses.tigetstr("setf")isnotNoneand curses.tigetstr("setb")isnotNone)or(curses.tigetstr("setaf")isnotNoneand curses.tigetstr("setab")isnotNone)or curses.tigetstr("scp")isnotNone)):return1except Exception:pass# 其他情况返回 0return0# 如果终端支持颜色,则定义颜色代码和文本修饰函数if terminal_has_colors():
_colour_codes =dict(black=0, red=1, green=2, yellow=3,
blue=4, magenta=5, cyan=6, white=7, default=9)defcolour_text(s, fg=None, bg=None, bold=False):
seq =[]# 如果 bold 为真,则加入 '1' 到序列中if bold:
seq.append('1')# 如果 fg 存在,则根据颜色返回对应的代码if fg:
fgcode =30+ _colour_codes.get(fg.lower(),0)
seq.append(str(fgcode))# 如果 bg 存在,则根据颜色返回对应的代码if bg:
bgcode =40+ _colour_codes.get(bg.lower(),7)
seq.append(str(bgcode))# 如果有需要修改文本颜色的指令,则返回修改后的文本,否则返回原始文本if seq:return'\x1b[%sm%s\x1b[0m'%(';'.join(seq), s)else:return s
else:# 如果终端不支持颜色,则定义文本颜色修改函数defcolour_text(s, fg=None, bg=None):return s
# 定义默认文本颜色修改函数defdefault_text(s):return colour_text(s,'default')# 定义红色文本颜色修改函数defred_text(s):return colour_text(s,'red')# 定义绿色文本颜色修改函数defgreen_text(s):return colour_text(s,'green')# 定义黄色文本颜色修改函数defyellow_text(s):return colour_text(s,'yellow')# 定义青色文本颜色修改函数defcyan_text(s):return colour_text(s,'cyan')# 定义蓝色文本颜色修改函数defblue_text(s):return colour_text(s,'blue')# 将 cygwin 路径转换为 win32 路径defcyg2win32(path:str)->str:# 将路径从 Cygwin 本地格式转换为 Windows 本地格式# 使用 cygpath 工具(Base 安装的一部分)来进行实际转换。如果失败,则返回原始路径# 处理默认的“/cygdrive”挂载前缀,以及“/proc/cygdrive”便携前缀,自定义的 cygdrive 前缀,如“/”或“/mnt”,以及绝对路径,如“/usr/src/”或“/home/username”# 参数:# path:str,要转换的路径# 返回:# converted_path:str,转换后的路径# 注:# cygpath 工具的文档:# https://cygwin.com/cygwin-ug-net/cygpath.html# 它封装的 C 函数的文档:# https://cygwin.com/cygwin-api/func-cygwin-conv-path.htmlif sys.platform !="cygwin":# 如果不是在 Cygwin 平台上,直接返回原始路径return path
# 调用子进程执行 cygpath 命令,传入参数"--windows"和路径,获取输出return subprocess.check_output(["/usr/bin/cygpath","--windows", path], text=True)# 判断是否在mingw32环境中defmingw32():"""Return true when using mingw32 environment.
"""# 如果操作系统是win32if sys.platform=='win32':# 如果环境变量OSTYPE的值是'msys'if os.environ.get('OSTYPE','')=='msys':returnTrue# 如果环境变量MSYSTEM的值是'MINGW32'if os.environ.get('MSYSTEM','')=='MINGW32':returnTrue# 如果以上条件都不满足,则返回FalsereturnFalse# 返回MSVC运行库的版本,由__MSC_VER__宏定义defmsvc_runtime_version():"Return version of MSVC runtime library, as defined by __MSC_VER__ macro"# 在sys.version中查找'MSC v.'的位置
msc_pos = sys.version.find('MSC v.')if msc_pos !=-1:# 如果找到'MSC v.',则获取其后6到10位的数字作为版本号
msc_ver =int(sys.version[msc_pos+6:msc_pos+10])else:# 如果没有找到'MSC v.',则版本号为None
msc_ver =Nonereturn msc_ver
# 返回Python是否使用MSVC构建的MSVC运行库的名称defmsvc_runtime_library():"Return name of MSVC runtime library if Python was built with MSVC >= 7"# 获取MSVC运行库的主要版本号
ver = msvc_runtime_major ()if ver:# 如果版本号小于140,返回'msvcr'加上版本号的字符串if ver <140:return"msvcr%i"% ver
# 如果版本号大于等于140,返回'vcruntime'加上版本号的字符串else:return"vcruntime%i"% ver
else:# 如果没有版本号,返回NonereturnNone# 返回MSVC运行库的主要版本号defmsvc_runtime_major():"Return major version of MSVC runtime coded like get_build_msvc_version"# 构建一个字典,包含MSVC运行库版本号与主要版本号的对应关系
major ={1300:70,# MSVC 7.01310:71,# MSVC 7.11400:80,# MSVC 81500:90,# MSVC 9 (aka 2008)1600:100,# MSVC 10 (aka 2010)1900:140,# MSVC 14 (aka 2015)}.get(msvc_runtime_version(),None)return major
##########################XXX 需要支持同时为C和C++的文件.C
cxx_ext_match = re.compile(r'.*\.(cpp|cxx|cc)\Z', re.I).match
fortran_ext_match = re.compile(r'.*\.(f90|f95|f77|for|ftn|f)\Z', re.I).match
f90_ext_match = re.compile(r'.*\.(f90|f95)\Z', re.I).match
f90_module_name_match = re.compile(r'\s*module\s*(?P<name>[\w_]+)', re.I).match# 获取Fortran f90模块的名称列表def_get_f90_modules(source):"""Return a list of Fortran f90 module names that
given source file defines.
"""# 如果给定的源文件不是f90格式,返回空列表ifnot f90_ext_match(source):return[]
modules =[]withopen(source)as f:for line in f:# 匹配并提取f90模块的名称
m = f90_module_name_match(line)if m:
name = m.group('name')
modules.append(name)# break # XXX can we assume that there is one module per file?return modules
# 判断一个对象是否是字符串defis_string(s):returnisinstance(s,str)# 判断列表中的所有项是否都是字符串对象defall_strings(lst):"""Return True if all items in lst are string objects. """for item in lst:ifnot is_string(item):returnFalsereturnTrue# 判断一个对象是否是序列(即可迭代的对象,如列表、元组、字符串)defis_sequence(seq):if is_string(seq):returnFalsetry:len(seq)except Exception:returnFalsereturnTrue# 判断一个字符串是否是glob模式(带*或?的字符串)defis_glob_pattern(s):return is_string(s)and('*'in s or'?'in s)# 将一个对象转换为列表defas_list(seq):if is_sequence(seq):returnlist(seq)else:return[seq]# 获取源文件的语言类型defget_language(sources):# not used in numpy/scipy packages, use build_ext.detect_language instead"""Determine language value (c,f77,f90) from sources """
language =None# 遍历给定的源列表 sourcesfor source in sources:# 检查当前源是否是字符串类型ifisinstance(source,str):# 如果当前源的文件扩展名匹配 Fortran 90 的扩展名if f90_ext_match(source):# 设置语言类型为 'f90'
language ='f90'# 跳出循环,已确定语言类型break# 如果当前源的文件扩展名匹配 Fortran 77 的扩展名elif fortran_ext_match(source):# 设置语言类型为 'f77'
language ='f77'# 返回确定的语言类型return language
# 检查给定的源文件列表中是否包含 Fortran 文件,如果有则返回 True,否则返回 Falsedefhas_f_sources(sources):for source in sources:# 调用 fortran_ext_match 函数检查文件名是否匹配 Fortran 文件扩展名if fortran_ext_match(source):returnTruereturnFalse# 检查给定的源文件列表中是否包含 C++ 文件,如果有则返回 True,否则返回 Falsedefhas_cxx_sources(sources):for source in sources:# 调用 cxx_ext_match 函数检查文件名是否匹配 C++ 文件扩展名if cxx_ext_match(source):returnTruereturnFalse# 对给定的源文件列表进行过滤,返回四个文件名列表:C 文件、C++ 文件、Fortran 文件、Fortran 90 模块文件deffilter_sources(sources):
c_sources =[]
cxx_sources =[]
f_sources =[]
fmodule_sources =[]for source in sources:if fortran_ext_match(source):# 如果文件名匹配 Fortran 文件扩展名,则进一步检查是否是 Fortran 90 模块
modules = _get_f90_modules(source)if modules:
fmodule_sources.append(source)else:
f_sources.append(source)elif cxx_ext_match(source):
cxx_sources.append(source)else:
c_sources.append(source)return c_sources, cxx_sources, f_sources, fmodule_sources
# 从目录列表中获取所有的 *.h 文件,并返回一个包含这些文件名的列表def_get_headers(directory_list):
headers =[]for d in directory_list:# 使用 sorted_glob 函数获取指定目录下的所有 *.h 文件,并将结果添加到 headers 列表中
head = sorted_glob(os.path.join(d,"*.h"))# XXX: *.hpp files??
headers.extend(head)return headers
# 从源文件列表中获取所有文件的父目录,并返回一个包含这些目录名的列表def_get_directories(list_of_sources):
direcs =[]for f in list_of_sources:# 使用 os.path.split 函数获取文件的父目录,并确保目录名不重复
d = os.path.split(f)if d[0]!=''andnot d[0]in direcs:
direcs.append(d[0])return direcs
# 构造用于确定是否需要重新编译文件的命令行表示,并返回该字符串def_commandline_dep_string(cc_args, extra_postargs, pp_opts):
cmdline ='commandline: '
cmdline +=' '.join(cc_args)
cmdline +=' '.join(extra_postargs)
cmdline +=' '.join(pp_opts)+'\n'return cmdline
# 分析源文件列表中的包含语句,获取所有被包含的头文件,并返回一个包含这些头文件名的列表defget_dependencies(sources):# 调用 _get_directories 函数获取源文件列表中所有文件的父目录列表,然后调用 _get_headers 获取这些目录中的头文件return _get_headers(_get_directories(sources))# 检查目录是否是本地目录,并返回 True 或 Falsedefis_local_src_dir(directory):ifnot is_string(directory):returnFalse
abs_dir = os.path.abspath(directory)
c = os.path.commonprefix([os.getcwd(), abs_dir])
new_dir = abs_dir[len(c):].split(os.sep)if new_dir andnot new_dir[0]:
new_dir = new_dir[1:]if new_dir and new_dir[0]=='build':returnFalse
new_dir = os.sep.join(new_dir)return os.path.isdir(new_dir)# 生成指定路径下的源文件列表,排除特定的目录和文件类型,使用生成器实现defgeneral_source_files(top_path):
pruned_directories ={'CVS':1,'.svn':1,'build':1}
prune_file_pat = re.compile(r'(?:[~#]|\.py[co]|\.o)$')for dirpath, dirnames, filenames in os.walk(top_path, topdown=True):
pruned =[ d for d in dirnames if d notin pruned_directories ]
dirnames[:]= pruned
for f in filenames:# 排除指定文件类型的文件,并生成文件的完整路径ifnot prune_file_pat.search(f):yield os.path.join(dirpath, f)# 生成指定路径下的源文件目录列表和文件列表,排除特定的目录和文件类型,使用生成器实现defgeneral_source_directories_files(top_path):# 返回相对于 top_path 的目录名和包含的文件列表"""Return a directory name relative to top_path and
files contained.
"""# 要忽略的目录列表,不包含在结果中
pruned_directories =['CVS','.svn','build']# 用于匹配需要剔除的文件模式的正则表达式
prune_file_pat = re.compile(r'(?:[~#]|\.py[co]|\.o)$')# 从 top_path 开始,递归遍历文件系统中的目录for dirpath, dirnames, filenames in os.walk(top_path, topdown=True):# 从当前目录的子目录列表中剔除 pruned_directories 中的目录
pruned =[d for d in dirnames if d notin pruned_directories]
dirnames[:]= pruned # 更新 dirnames 列表,以便下一步的遍历不包含被剔除的目录for d in dirnames:# 构建子目录的完整路径
dpath = os.path.join(dirpath, d)# 计算子目录相对于 top_path 的相对路径
rpath = rel_path(dpath, top_path)
files =[]# 遍历子目录中的文件列表for f in os.listdir(dpath):
fn = os.path.join(dpath, f)# 如果文件是普通文件且不匹配 prune_file_pat 的模式,则加入 files 列表if os.path.isfile(fn)andnot prune_file_pat.search(fn):
files.append(fn)# 生成相对路径 rpath 和文件列表 files 的元组yield rpath, files
# 处理 top_path 目录本身,生成其相对路径和包含的文件列表
dpath = top_path
# 计算 top_path 目录相对于自身的路径(即空字符串)
rpath = rel_path(dpath, top_path)# 获取 top_path 目录下所有文件的完整路径列表
filenames =[os.path.join(dpath, f)for f in os.listdir(dpath)ifnot prune_file_pat.search(f)]# 过滤出 filenames 中真正的文件路径(不是目录路径)
files =[f for f in filenames if os.path.isfile(f)]# 生成相对路径 rpath 和文件列表 files 的元组yield rpath, files
# 返回具有指定扩展名的源文件及同一目录中的任何包含文件defget_ext_source_files(ext):# 创建空文件名列表
filenames =[]# 获取所有源文件
sources =[_m for _m in ext.sources if is_string(_m)]# 将源文件添加到文件名列表中
filenames.extend(sources)# 获取源文件的依赖项,并将其添加到文件名列表中
filenames.extend(get_dependencies(sources))# 遍历依赖列表for d in ext.depends:# 如果依赖是本地源文件目录,则将其下的所有通用源文件添加到文件名列表中if is_local_src_dir(d):
filenames.extend(list(general_source_files(d)))# 如果依赖是文件,则将该文件添加到文件名列表中elif os.path.isfile(d):
filenames.append(d)return filenames
# 获取脚本文件defget_script_files(scripts):# 获取所有脚本文件并返回
scripts =[_m for _m in scripts if is_string(_m)]return scripts
# 返回库的源文件defget_lib_source_files(lib):# 创建空文件名列表
filenames =[]# 获取库的源文件
sources = lib[1].get('sources',[])# 将源文件添加到文件名列表中
sources =[_m for _m in sources if is_string(_m)]
filenames.extend(sources)# 获取源文件的依赖项,并将其添加到文件名列表中
filenames.extend(get_dependencies(sources))# 获取库的依赖项
depends = lib[1].get('depends',[])for d in depends:# 如果依赖是本地源文件目录,则将其下的所有通用源文件添加到文件名列表中if is_local_src_dir(d):
filenames.extend(list(general_source_files(d)))# 如果依赖是文件,则将该文件添加到文件名列表中elif os.path.isfile(d):
filenames.append(d)return filenames
# 获取共享库的扩展名defget_shared_lib_extension(is_python_ext=False):"""Return the correct file extension for shared libraries.
Parameters
----------
is_python_ext : bool, optional
Whether the shared library is a Python extension. Default is False.
Returns
-------
so_ext : str
The shared library extension.
Notes
-----
For Python shared libs, `so_ext` will typically be '.so' on Linux and OS X,
and '.pyd' on Windows. For Python >= 3.2 `so_ext` has a tag prepended on
POSIX systems according to PEP 3149.
"""# 获取配置变量
confvars = distutils.sysconfig.get_config_vars()# 获取共享库的扩展名
so_ext = confvars.get('EXT_SUFFIX','')# 如果不是Python扩展,则根据操作系统返回正确的共享库扩展名ifnot is_python_ext:# 硬编码已知的值,配置变量(包括SHLIB_SUFFIX)不可靠(参见#3182)# 在3.3.1及更早版本中,darwin,windows和debug linux是错误的if(sys.platform.startswith('linux')or
sys.platform.startswith('gnukfreebsd')):
so_ext ='.so'elif sys.platform.startswith('darwin'):
so_ext ='.dylib'elif sys.platform.startswith('win'):
so_ext ='.dll'else:# 对于未知平台,回退到配置变量# 修复Python> = 3.2的长扩展,参见PEP 3149。if'SOABI'in confvars:# 除非存在SOABI配置变量,否则不执行任何操作
so_ext = so_ext.replace('.'+ confvars.get('SOABI'),'',1)return so_ext
# 获取数据文件defget_data_files(data):# 如果数据是字符串,则返回其列表if is_string(data):return[data]# 否则,获取数据源并创建空文件名列表
sources = data[1]
filenames =[]# 对于给定的源列表中的每个元素进行迭代for s in sources:# 检查当前元素是否是可调用的对象,如果是,则跳过本次迭代ifhasattr(s,'__call__'):continue# 检查当前元素是否是本地源目录路径if is_local_src_dir(s):# 将该目录下所有一般源文件的文件名添加到文件名列表中
filenames.extend(list(general_source_files(s)))# 如果当前元素是字符串类型elif is_string(s):# 检查该字符串是否是一个文件路径if os.path.isfile(s):# 将文件路径添加到文件名列表中
filenames.append(s)else:# 打印出文件路径不存在的警告信息print('Not existing data file:', s)else:# 如果当前元素不是可调用对象,也不是本地源目录路径,也不是字符串,则引发类型错误raise TypeError(repr(s))# 返回最终的文件名列表return filenames
defdot_join(*args):# 将传入的参数按照"."连接成一个字符串return'.'.join([a for a in args if a])defget_frame(level=0):"""Return frame object from call stack with given level.
"""try:# 返回调用栈中指定层级的帧对象return sys._getframe(level+1)except AttributeError:# 如果没有找到指定层级的帧对象,返回当前异常的帧对象
frame = sys.exc_info()[2].tb_frame
for _ inrange(level+1):
frame = frame.f_back
return frame
######################classConfiguration:
_list_keys =['packages','ext_modules','data_files','include_dirs','libraries','headers','scripts','py_modules','installed_libraries','define_macros']
_dict_keys =['package_dir','installed_pkg_config']
_extra_keys =['name','version']
numpy_include_dirs =[]deftodict(self):"""
Return a dictionary compatible with the keyword arguments of distutils
setup function.
Examples
--------
>>> setup(**config.todict()) #doctest: +SKIP
"""# 优化数据文件
self._optimize_data_files()
d ={}# 创建一个空字典
known_keys = self.list_keys + self.dict_keys + self.extra_keys
# 将配置中的列表、字典和额外的键合并在一起for n in known_keys:
a =getattr(self, n)if a:# 如果属性值不为空,则将属性名和属性值添加到字典中
d[n]= a
return d
definfo(self, message):ifnot self.options['quiet']:# 如果选项中的quiet值为False,则输出消息print(message)defwarn(self, message):
sys.stderr.write('Warning: %s\n'%(message,))# 输出警告信息到标准错误流defset_options(self,**options):"""
Configure Configuration instance.
The following options are available:
- ignore_setup_xxx_py
- assume_default_configuration
- delegate_options_to_subpackages
- quiet
"""for key, value in options.items():if key in self.options:# 如果选项名存在于配置中,则将选项值赋给配置的对应选项
self.options[key]= value
else:# 如果选项名不存在于配置中,则抛出值错误异常raise ValueError('Unknown option: '+key)defget_distribution(self):"""Return the distutils distribution object for self."""from numpy.distutils.core import get_distribution
# 导入并返回相应的distutils分发对象return get_distribution()def_wildcard_get_subpackage(self, subpackage_name,
parent_name,
caller_level =1):
l = subpackage_name.split('.')
subpackage_path = njoin([self.local_path]+l)
dirs =[_m for _m in sorted_glob(subpackage_path)if os.path.isdir(_m)]
config_list =[]for d in dirs:ifnot os.path.isfile(njoin(d,'__init__.py')):continueif'build'in d.split(os.sep):continue
n ='.'.join(d.split(os.sep)[-len(l):])
c = self.get_subpackage(n,
parent_name = parent_name,
caller_level = caller_level+1)
config_list.extend(c)return config_list
# 从 setup.py 文件中获取配置信息def_get_configuration_from_setup_py(self, setup_py,
subpackage_name,
subpackage_path,
parent_name,
caller_level =1):# 为了防止 setup.py 引入本地模块,将其所在目录添加到 sys.path 中
sys.path.insert(0, os.path.dirname(setup_py)try:# 获取 setup.py 文件名(不带扩展名)
setup_name = os.path.splitext(os.path.basename(setup_py))[0]# 组合模块名
n = dot_join(self.name, subpackage_name, setup_name)# 从指定位置执行模块
setup_module = exec_mod_from_location('_'.join(n.split('.')), setup_py)# 如果模块没有定义 configuration 属性ifnothasattr(setup_module,'configuration'):# 如果不假设默认配置,则警告ifnot self.options['assume_default_configuration']:
self.warn('Assuming default configuration (%s does not define configuration())'%(setup_module))# 创建默认配置
config = Configuration(subpackage_name, parent_name, self.top_path, subpackage_path, caller_level = caller_level +1)else:# 组合父模块名
pn = dot_join(*([parent_name]+ subpackage_name.split('.')[:-1]))
args =(pn,)# 如果 configuration 函数参数大于 1,需要传入 self.top_pathif setup_module.configuration.__code__.co_argcount >1:
args = args +(self.top_path,)# 调用 setup_module 中的 configuration 函数
config = setup_module.configuration(*args)# 如果配置的名称不与父模块和子模块的组合名称相同,发出警告if config.name != dot_join(parent_name, subpackage_name):
self.warn('Subpackage %r configuration returned as %r'%(dot_join(parent_name, subpackage_name), config.name))finally:# 在 finally 块中删除刚刚添加的路径del sys.path[0]# 返回获取到的配置return config
# 返回子包的配置列表defget_subpackage(self,subpackage_name,
subpackage_path=None,
parent_name=None,
caller_level =1):"""Return list of subpackage configurations.
Parameters
----------
subpackage_name : str or None
子包的名称用于获取配置。在subpackage_name中的'*'将被处理为通配符。
subpackage_path : str
如果为None,则假定路径为本地路径加上subpackage_name。如果在subpackage_path中找不到setup.py文件,则使用默认配置。
parent_name : str
父名称。
"""if subpackage_name isNone:if subpackage_path isNone:raise ValueError("either subpackage_name or subpackage_path must be specified")
subpackage_name = os.path.basename(subpackage_path)# 处理通配符
l = subpackage_name.split('.')if subpackage_path isNoneand'*'in subpackage_name:return self._wildcard_get_subpackage(subpackage_name,
parent_name,
caller_level = caller_level+1)assert'*'notin subpackage_name,repr((subpackage_name, subpackage_path, parent_name))if subpackage_path isNone:
subpackage_path = njoin([self.local_path]+ l)else:
subpackage_path = njoin([subpackage_path]+ l[:-1])
subpackage_path = self.paths([subpackage_path])[0]
setup_py = njoin(subpackage_path, self.setup_name)ifnot self.options['ignore_setup_xxx_py']:ifnot os.path.isfile(setup_py):
setup_py = njoin(subpackage_path,'setup_%s.py'%(subpackage_name))ifnot os.path.isfile(setup_py):ifnot self.options['assume_default_configuration']:
self.warn('Assuming default configuration '\
'(%s/{setup_%s,setup}.py was not found)' \
%(os.path.dirname(setup_py), subpackage_name))
config = Configuration(subpackage_name, parent_name,
self.top_path, subpackage_path,
caller_level = caller_level+1)else:
config = self._get_configuration_from_setup_py(
setup_py,
subpackage_name,
subpackage_path,
parent_name,
caller_level = caller_level +1)if config:return[config]else:return[]# 添加一个子包到当前的 Configuration 实例defadd_subpackage(self,subpackage_name,
subpackage_path=None,
standalone =False):"""Add a sub-package to the current Configuration instance.
This is useful in a setup.py script for adding sub-packages to a
package.
Parameters
----------
subpackage_name : str
name of the subpackage
subpackage_path : str
if given, the subpackage path such as the subpackage is in
subpackage_path / subpackage_name. If None,the subpackage is
assumed to be located in the local path / subpackage_name.
standalone : bool
"""if standalone:
parent_name =Noneelse:
parent_name = self.name
config_list = self.get_subpackage(subpackage_name, subpackage_path,
parent_name = parent_name,
caller_level =2)ifnot config_list:
self.warn('No configuration returned, assuming unavailable.')for config in config_list:
d = config
ifisinstance(config, Configuration):
d = config.todict()assertisinstance(d,dict),repr(type(d))
self.info('Appending %s configuration to %s' \
%(d.get('name'), self.name))
self.dict_append(**d)# 获取分发情况
dist = self.get_distribution()if dist isnotNone:
self.warn('distutils distribution has been initialized,'\
' it may be too late to add a subpackage '+ subpackage_name)# 优化数据文件def_optimize_data_files(self):
data_dict ={}for p, files in self.data_files:if p notin data_dict:
data_dict[p]=set()for f in files:
data_dict[p].add(f)
self.data_files[:]=[(p,list(files))for p, files in data_dict.items()]### XXX Implement add_py_modules# 添加宏定义到配置defadd_define_macros(self, macros):"""Add define macros to configuration
Add the given sequence of macro name and value duples to the beginning
of the define_macros list. This list will be visible to all extension
modules of the current package.
"""
dist = self.get_distribution()if dist isnotNone:ifnothasattr(dist,'define_macros'):
dist.define_macros =[]
dist.define_macros.extend(macros)else:
self.define_macros.extend(macros)# 将给定的路径添加到配置的包含目录中defadd_include_dirs(self,*paths):"""
Add paths to configuration include directories.
Add the given sequence of paths to the beginning of the include_dirs
list. This list will be visible to all extension modules of the
current package.
"""# 将给定的路径转换成包含目录
include_dirs = self.paths(paths)# 获取当前包的分发对象
dist = self.get_distribution()# 如果有分发对象,则将包含目录添加到分发对象中if dist isnotNone:if dist.include_dirs isNone:
dist.include_dirs =[]
dist.include_dirs.extend(include_dirs)# 如果没有分发对象,则将包含目录添加到当前对象中else:
self.include_dirs.extend(include_dirs)# 将可安装的头文件添加到配置中defadd_headers(self,*files):"""
Add installable headers to configuration.
Add the given sequence of files to the beginning of the headers list.
By default, headers will be installed under <python-
include>/<self.name.replace('.','/')>/ directory. If an item of files
is a tuple, then its first argument specifies the actual installation
location relative to the <python-include> path.
Parameters
----------
files : str or seq
Argument(s) can be either:
* 2-sequence (<includedir suffix>,<path to header file(s)>)
* path(s) to header file(s) where python includedir suffix will
default to package name.
"""
headers =[]# 遍历文件路径,根据类型进行处理for path in files:if is_string(path):[headers.append((self.name, p))for p in self.paths(path)]else:ifnotisinstance(path,(tuple,list))orlen(path)!=2:raise TypeError(repr(path))[headers.append((path[0], p))for p in self.paths(path[1])]# 获取当前包的分发对象
dist = self.get_distribution()# 如果有分发对象,则将头文件添加到分发对象中if dist isnotNone:if dist.headers isNone:
dist.headers =[]
dist.headers.extend(headers)# 如果没有分发对象,则将头文件添加到当前对象中else:
self.headers.extend(headers)# 将路径应用于全局路径,并添加本地路径(如果需要的话)defpaths(self,*paths,**kws):"""
Apply glob to paths and prepend local_path if needed.
Applies glob.glob(...) to each path in the sequence (if needed) and
pre-pends the local_path if needed. Because this is called on all
source lists, this allows wildcard characters to be specified in lists
of sources for extension modules and libraries and scripts and allows
path-names be relative to the source directory.
"""
include_non_existing = kws.get('include_non_existing',True)return gpaths(paths,
local_path = self.local_path,
include_non_existing=include_non_existing)# 修正路径字典def_fix_paths_dict(self, kw):for k in kw.keys():
v = kw[k]if k in['sources','depends','include_dirs','library_dirs','module_dirs','extra_objects']:
new_v = self.paths(v)
kw[k]= new_v
# 将库添加到配置中defadd_library(self,name,sources,**build_info):"""
Add library to configuration.
Parameters
----------
name : str
Name of the extension.
sources : sequence
List of the sources. The list of sources may contain functions
(called source generators) which must take an extension instance
and a build directory as inputs and return a source file or list of
source files or None. If None is returned then no sources are
generated. If the Extension instance has no sources after
processing all source generators, then no extension module is
built.
build_info : dict, optional
The following keys are allowed:
* depends
* macros
* include_dirs
* extra_compiler_args
* extra_f77_compile_args
* extra_f90_compile_args
* f2py_options
* language
"""# 调用内部方法 _add_library,参数包括名称、源代码、安装目录和构建信息
self._add_library(name, sources,None, build_info)# 获取分发对象
dist = self.get_distribution()# 如果分发对象不为空则发出警告if dist isnotNone:
self.warn('distutils distribution has been initialized,'\
' it may be too late to add a library '+ name)# 内部方法,用于增加库和已安装库def_add_library(self, name, sources, install_dir, build_info):"""Common implementation for add_library and add_installed_library. Do
not use directly"""# 复制构建信息,将源代码添加到构建信息
build_info = copy.copy(build_info)
build_info['sources']= sources
# 有时候,依赖关系默认不为空列表,如果未给出依赖关系,则添加一个空列表ifnot'depends'in build_info:
build_info['depends']=[]# 修正路径字典
self._fix_paths_dict(build_info)# 将库添加到库列表中,以便与 build_clib 一起构建
self.libraries.append((name, build_info))# 定义一个方法,用于添加已安装的库defadd_installed_library(self, name, sources, install_dir, build_info=None):"""
Similar to add_library, but the specified library is installed.
Most C libraries used with ``distutils`` are only used to build python
extensions, but libraries built through this method will be installed
so that they can be reused by third-party packages.
Parameters
----------
name : str
Name of the installed library.
sources : sequence
List of the library's source files. See `add_library` for details.
install_dir : str
Path to install the library, relative to the current sub-package.
build_info : dict, optional
The following keys are allowed:
* depends
* macros
* include_dirs
* extra_compiler_args
* extra_f77_compile_args
* extra_f90_compile_args
* f2py_options
* language
Returns
-------
None
See Also
--------
add_library, add_npy_pkg_config, get_info
Notes
-----
The best way to encode the options required to link against the specified
C libraries is to use a "libname.ini" file, and use `get_info` to
retrieve the required options (see `add_npy_pkg_config` for more
information).
"""# 如果未提供构建信息,将构建信息设为空字典ifnot build_info:
build_info ={}# 将安装目录路径拼接到当前子包路径上
install_dir = os.path.join(self.package_path, install_dir)# 调用私有方法 _add_library,并传入参数
self._add_library(name, sources, install_dir, build_info)# 将已安装的库信息添加到已安装库列表中
self.installed_libraries.append(InstallableLib(name, build_info, install_dir))# 定义一个方法,用于添加脚本文件defadd_scripts(self,*files):"""Add scripts to configuration.
Add the sequence of files to the beginning of the scripts list.
Scripts will be installed under the <prefix>/bin/ directory.
"""# 将传入的文件路径转换为绝对路径
scripts = self.paths(files)# 获取当前的发行版
dist = self.get_distribution()# 如果发行版存在if dist isnotNone:# 如果发行版的脚本列表为None,则将其设为空列表if dist.scripts isNone:
dist.scripts =[]# 将文件列表添加到发行版的脚本列表中
dist.scripts.extend(scripts)else:# 如果发行版不存在,则将文件列表添加到当前对象的脚本列表中
self.scripts.extend(scripts)# 在字典属性中添加另一个字典的内容defdict_append(self,**dict):# 对于列表类型的属性,将传入的字典中对应的键的值添加到列表末尾for key in self.list_keys:
a =getattr(self, key)
a.extend(dict.get(key,[]))# 对于字典类型的属性,将传入的字典中对应的键值对更新到字典中for key in self.dict_keys:
a =getattr(self, key)
a.update(dict.get(key,{}))# 获取已知的键的列表
known_keys = self.list_keys + self.dict_keys + self.extra_keys
# 循环遍历传入的字典中的键for key indict.keys():# 如果键不在已知的键列表中if key notin known_keys:# 获取属性的值
a =getattr(self, key,None)# 如果值存在且与传入字典中对应键的值相等,则不做任何操作if a and a==dict[key]:continue# 否则,发出警告并更新属性的值
self.warn('Inheriting attribute %r=%r from %r' \
%(key,dict[key],dict.get('name','?')))setattr(self, key,dict[key])# 更新额外的键列表
self.extra_keys.append(key)# 如果键在额外的键列表中elif key in self.extra_keys:# 发出信息,忽略设置属性的尝试
self.info('Ignoring attempt to set %r (from %r to %r)' \
%(key,getattr(self, key),dict[key]))# 如果键在已知的键列表中elif key in known_keys:# 键已在上面处理过,不做任何操作pass# 如果键未知else:# 抛出异常raise ValueError("Don't know about key=%r"%(key))# 返回实例的字符串表示def__str__(self):from pprint import pformat
# 获取已知的键列表
known_keys = self.list_keys + self.dict_keys + self.extra_keys
# 初始化字符串
s ='<'+5*'-'+'\n'
s +='Configuration of '+self.name+':\n'# 对已知的键列表进行排序
known_keys.sort()# 遍历已知的键列表for k in known_keys:# 获取属性的值
a =getattr(self, k,None)# 如果属性的值存在if a:# 将属性的键值对格式化为字符串,并添加到s中
s +='%s = %s\n'%(k, pformat(a))
s +=5*'-'+'>'return s
# 返回numpy.distutils的配置命令实例defget_config_cmd(self):"""
返回numpy.distutils配置命令实例。
"""
cmd = get_cmd('config')
cmd.ensure_finalized()
cmd.dump_source =0
cmd.noisy =0
old_path = os.environ.get('PATH')if old_path:
path = os.pathsep.join(['.', old_path])
os.environ['PATH']= path
return cmd
# 返回临时构建文件的路径defget_build_temp_dir(self):"""
返回一个临时目录的路径,临时文件应该放在其中。
"""
cmd = get_cmd('build')
cmd.ensure_finalized()return cmd.build_temp
# 检查Fortran 77编译器的可用性defhave_f77c(self):"""Check for availability of Fortran 77 compiler.
在源代码生成函数中使用它,以确保已初始化设置分发实例。
Notes
-----
如果Fortran 77编译器可用(因为能够成功编译简单的Fortran 77代码),则返回True。
"""# 简单的Fortran 77子例程
simple_fortran_subroutine ='''
subroutine simple
end
'''# 获取配置命令
config_cmd = self.get_config_cmd()# 尝试编译简单的Fortran 77子例程,返回是否成功的标志
flag = config_cmd.try_compile(simple_fortran_subroutine, lang='f77')return flag
# 检查是否有 Fortran 90 编译器可用defhave_f90c(self):"""Check for availability of Fortran 90 compiler.
Use it inside source generating function to ensure that
setup distribution instance has been initialized.
Notes
-----
True if a Fortran 90 compiler is available (because a simple Fortran
90 code was able to be compiled successfully)
"""# 创建简单的 Fortran 90 子例程代码
simple_fortran_subroutine ='''
subroutine simple
end
'''# 获取配置命令
config_cmd = self.get_config_cmd()# 尝试编译简单的 Fortran 90 子例程代码,并返回编译结果
flag = config_cmd.try_compile(simple_fortran_subroutine, lang='f90')return flag
# 向扩展或库项目的库和包含路径中添加库defappend_to(self, extlib):"""Append libraries, include_dirs to extension or library item.
"""# 如果 extlib 是序列if is_sequence(extlib):
lib_name, build_info = extlib
# 向 build_info 的 libraries 和 include_dirs 中添加库和包含路径
dict_append(build_info,
libraries=self.libraries,
include_dirs=self.include_dirs)else:from numpy.distutils.core import Extension
assertisinstance(extlib, Extension),repr(extlib)
extlib.libraries.extend(self.libraries)
extlib.include_dirs.extend(self.include_dirs)# 获取路径的 SVN 版本号def_get_svn_revision(self, path):"""Return path's SVN revision number.
"""try:# 使用 subprocess 模块获取 SVN 版本号
output = subprocess.check_output(['svnversion'], cwd=path)except(subprocess.CalledProcessError, OSError):passelse:# 使用正则表达式匹配 SVN 版本号
m = re.match(rb'(?P<revision>\d+)', output)if m:returnint(m.group('revision'))# 如果是 Windows 平台并且存在 SVN_ASP_DOT_NET_HACK 环境变量if sys.platform=='win32'and os.environ.get('SVN_ASP_DOT_NET_HACK',None):
entries = njoin(path,'_svn','entries')else:
entries = njoin(path,'.svn','entries')# 如果文件存在,打开它并读取内容if os.path.isfile(entries):withopen(entries)as f:
fstr = f.read()# 如果文件内容是以 '<?xml' 开头,使用正则表达式查找 SVN 版本号if fstr[:5]=='<?xml':# pre 1.4
m = re.search(r'revision="(?P<revision>\d+)"', fstr)if m:returnint(m.group('revision'))else:# 非 xml 格式的文件,检查内容中的 SVN 版本号
m = re.search(r'dir[\n\r]+(?P<revision>\d+)', fstr)if m:returnint(m.group('revision'))# 如果以上条件都不满足,返回 NonereturnNone# 定义一个方法用于获取指定路径下 Mercurial 版本控制系统的版本号def_get_hg_revision(self, path):"""Return path's Mercurial revision number.
"""try:# 尝试运行命令行程序 'hg identify --num' 来获取版本号
output = subprocess.check_output(['hg','identify','--num'], cwd=path)except(subprocess.CalledProcessError, OSError):# 如果出现异常(命令执行错误或系统错误),则忽略异常,不做任何操作passelse:# 如果成功获取到输出,使用正则表达式匹配输出中的版本号
m = re.match(rb'(?P<revision>\d+)', output)if m:# 如果匹配成功,返回匹配到的版本号转换为整数类型returnint(m.group('revision'))# 构造分支信息文件路径和缓存文件路径
branch_fn = njoin(path,'.hg','branch')
branch_cache_fn = njoin(path,'.hg','branch.cache')# 如果分支信息文件存在if os.path.isfile(branch_fn):
branch0 =None# 打开分支信息文件并读取第一行作为当前分支信息withopen(branch_fn)as f:
revision0 = f.read().strip()# 初始化一个空的分支映射字典
branch_map ={}# 打开分支缓存文件,逐行读取分支和版本信息,构建分支到版本号的映射withopen(branch_cache_fn)as f:for line in f:
branch1, revision1 = line.split()[:2]# 如果缓存中的版本号与当前版本号相同,则认为找到了当前分支if revision1 == revision0:
branch0 = branch1
try:# 尝试将版本号转换为整数类型,如果失败则继续下一次循环
revision1 =int(revision1)except ValueError:continue# 将分支和对应的版本号加入映射字典中
branch_map[branch1]= revision1
# 返回当前分支对应的版本号(如果找到的话)return branch_map.get(branch0)# 如果没有找到分支信息文件,则返回空值 NonereturnNonedefget_version(self, version_file=None, version_variable=None):"""尝试获取包的版本字符串。
如果无法检测到版本信息,则返回当前包的版本字符串或 None。
Notes
-----
该方法扫描文件 __version__.py、<packagename>_version.py、version.py、
和 __svn_version__.py,查找字符串变量 version、__version__ 和
<packagename>_version,直到找到版本号为止。
"""# 尝试从对象属性中获取版本号
version =getattr(self,'version',None)if version isnotNone:return version
# 从版本文件中获取版本号if version_file isNone:
files =['__version__.py',
self.name.split('.')[-1]+'_version.py','version.py','__svn_version__.py','__hg_version__.py']else:
files =[version_file]# 指定版本变量名if version_variable isNone:
version_vars =['version','__version__',
self.name.split('.')[-1]+'_version']else:
version_vars =[version_variable]# 遍历文件列表,尝试获取版本号for f in files:
fn = njoin(self.local_path, f)if os.path.isfile(fn):
info =('.py','U',1)
name = os.path.splitext(os.path.basename(fn))[0]
n = dot_join(self.name, name)try:# 从指定位置执行模块
version_module = exec_mod_from_location('_'.join(n.split('.')), fn)except ImportError as e:
self.warn(str(e))
version_module =None# 如果模块为空,继续下一个文件if version_module isNone:continue# 尝试从模块中获取版本变量for a in version_vars:
version =getattr(version_module, a,None)if version isnotNone:break# 尝试使用 versioneer 模块获取版本号try:
version = version_module.get_versions()['version']except AttributeError:passif version isnotNone:break# 如果找到版本号,则设置对象属性并返回版本号if version isnotNone:
self.version = version
return version
# 尝试获取 SVN 或 Mercurial 的修订号作为版本号
revision = self._get_svn_revision(self.local_path)if revision isNone:
revision = self._get_hg_revision(self.local_path)# 如果获取到修订号,则将其转换为字符串并设置对象属性if revision isnotNone:
version =str(revision)
self.version = version
# 返回最终获取的版本号return version
defmake_svn_version_py(self, delete=True):"""为 data_files 列表追加一个数据函数,用于生成当前包目录下的 __svn_version__.py 文件。
从 SVN 的修订版本号生成包的 __svn_version__.py 文件,
它在 Python 退出时将被删除,但在执行 sdist 等命令时将可用。
注意
-----
如果 __svn_version__.py 文件已存在,则不执行任何操作。
这个方法适用于带有 SVN 仓库的源代码目录。
"""
target = njoin(self.local_path,'__svn_version__.py')# 获取当前目录下的 SVN 修订版本号
revision = self._get_svn_revision(self.local_path)# 如果目标文件已经存在或者无法获取到 SVN 修订版本号,则直接返回if os.path.isfile(target)or revision isNone:returnelse:defgenerate_svn_version_py():# 如果目标文件不存在,则创建ifnot os.path.isfile(target):
version =str(revision)
self.info('Creating %s (version=%r)'%(target, version))# 写入修订版本号到目标文件withopen(target,'w')as f:
f.write('version = %r\n'%(version))# 定义一个函数,用于删除目标文件及其编译后的版本defrm_file(f=target, p=self.info):if delete:try:
os.remove(f)
p('removed '+ f)except OSError:passtry:
os.remove(f +'c')
p('removed '+ f +'c')except OSError:pass# 在程序退出时注册删除函数
atexit.register(rm_file)return target
# 将生成 __svn_version__.py 的函数添加到 data_files 列表中
self.add_data_files(('', generate_svn_version_py()))# 为当前类定义一个方法,用于生成 __hg_version__.py 文件并添加到 data_files 列表中defmake_hg_version_py(self, delete=True):"""Appends a data function to the data_files list that will generate
__hg_version__.py file to the current package directory.
Generate package __hg_version__.py file from Mercurial revision,
it will be removed after python exits but will be available
when sdist, etc commands are executed.
Notes
-----
If __hg_version__.py existed before, nothing is done.
This is intended for working with source directories that are
in an Mercurial repository.
"""# 指定目标文件路径为当前包目录下的 __hg_version__.py
target = njoin(self.local_path,'__hg_version__.py')# 获取 Mercurial 版本信息
revision = self._get_hg_revision(self.local_path)# 如果目标文件已存在或者无法获取到版本信息,则直接返回if os.path.isfile(target)or revision isNone:returnelse:# 定义生成 __hg_version__.py 文件的函数defgenerate_hg_version_py():# 如果目标文件不存在,则创建文件并写入版本信息ifnot os.path.isfile(target):
version =str(revision)
self.info('Creating %s (version=%r)'%(target, version))withopen(target,'w')as f:
f.write('version = %r\n'%(version))# 定义删除文件的函数defrm_file(f=target, p=self.info):# 如果 delete 标志为 True,则尝试删除目标文件和其对应的编译文件if delete:try:
os.remove(f)
p('removed '+ f)except OSError:passtry:
os.remove(f +'c')
p('removed '+ f +'c')except OSError:pass# 注册在程序退出时执行删除文件操作
atexit.register(rm_file)return target
# 将生成 __hg_version__.py 文件的函数添加到数据文件列表中
self.add_data_files(('', generate_hg_version_py()))# 为当前类定义一个方法,用于生成 __config__.py 文件并添加到 py_modules 列表中defmake_config_py(self, name='__config__'):"""Generate package __config__.py file containing system_info
information used during building the package.
This file is installed to the
package installation directory.
"""# 将生成 __config__.py 文件的函数添加到 py_modules 列表中
self.py_modules.append((self.name, name, generate_config_py))# 为当前类定义一个方法,用于获取多个资源信息并返回一个包含所有信息的字典defget_info(self,*names):"""Get resources information.
Return information (from system_info.get_info) for all of the names in
the argument list in a single dictionary.
"""# 导入必要的函数from.system_info import get_info, dict_append
# 初始化空字典用于存储信息
info_dict ={}# 遍历参数中的每个名称,调用 get_info 函数获取信息并添加到 info_dict 中for a in names:
dict_append(info_dict,**get_info(a))# 返回包含所有信息的字典return info_dict
# 根据命令名获取命令对象,使用缓存加速查找defget_cmd(cmdname, _cache={}):if cmdname notin _cache:# 导入distutils核心模块import distutils.core
# 获取distutils的设置分发对象
dist = distutils.core._setup_distribution
# 如果设置分发对象为None,则抛出内部错误异常if dist isNone:from distutils.errors import DistutilsInternalError
raise DistutilsInternalError('setup distribution instance not initialized')# 获取指定命令的命令对象
cmd = dist.get_command_obj(cmdname)# 将命令对象存入缓存
_cache[cmdname]= cmd
# 返回命令对象return _cache[cmdname]# 获取numpy包含目录defget_numpy_include_dirs():# 复制numpy_include_dirs列表内容
include_dirs = Configuration.numpy_include_dirs[:]# 如果列表为空,则导入numpy模块并获取其包含目录ifnot include_dirs:import numpy
include_dirs =[ numpy.get_include()]# 返回包含目录列表return include_dirs
# 获取npy-pkg-config目录路径defget_npy_pkg_dir():"""Return the path where to find the npy-pkg-config directory.
If the NPY_PKG_CONFIG_PATH environment variable is set, the value of that
is returned. Otherwise, a path inside the location of the numpy module is
returned.
The NPY_PKG_CONFIG_PATH can be useful when cross-compiling, maintaining
customized npy-pkg-config .ini files for the cross-compilation
environment, and using them when cross-compiling.
"""# 获取环境变量NPY_PKG_CONFIG_PATH的值
d = os.environ.get('NPY_PKG_CONFIG_PATH')# 如果环境变量不为None,则返回其值if d isnotNone:return d
# 否则,查找numpy模块的位置并构建npy-pkg-config目录路径
spec = importlib.util.find_spec('numpy')
d = os.path.join(os.path.dirname(spec.origin),'_core','lib','npy-pkg-config')# 返回npy-pkg-config目录路径return d
# 获取指定包名的库信息defget_pkg_info(pkgname, dirs=None):"""
Return library info for the given package.
Parameters
----------
pkgname : str
Name of the package (should match the name of the .ini file, without
the extension, e.g. foo for the file foo.ini).
dirs : sequence, optional
If given, should be a sequence of additional directories where to look
for npy-pkg-config files. Those directories are searched prior to the
NumPy directory.
Returns
-------
pkginfo : class instance
The `LibraryInfo` instance containing the build information.
Raises
------
PkgNotFound
If the package is not found.
See Also
--------
Configuration.add_npy_pkg_config, Configuration.add_installed_library,
get_info
"""# 导入numpy包配置模块中的read_config函数from numpy.distutils.npy_pkg_config import read_config
# 如果给定了dirs参数,则在其后追加npy-pkg-config目录路径if dirs:
dirs.append(get_npy_pkg_dir())else:# 否则,设置dirs为包含npy-pkg-config目录路径的列表
dirs =[get_npy_pkg_dir()]# 调用read_config函数读取指定包名的配置信息,并返回return read_config(pkgname, dirs)# 获取指定C库的信息字典defget_info(pkgname, dirs=None):"""
Return an info dict for a given C library.
The info dict contains the necessary options to use the C library.
Parameters
----------
pkgname : str
Name of the package (should match the name of the .ini file, without
the extension, e.g. foo for the file foo.ini).
"""# 这个函数的实现与注释部分完全相符,无需添加额外的代码注释pass# dirs: 可选的序列,如果提供,则应为寻找 npy-pkg-config 文件的附加目录序列。在 NumPy 目录之前搜索这些目录。# 返回值: 包含构建信息的字典。# 如果找不到包,则引发 PkgNotFound 异常。# 参见: Configuration.add_npy_pkg_config, Configuration.add_installed_library, get_pkg_info# 示例:# 要获取来自 NumPy 的 npymath 库的必要信息:# >>> npymath_info = np.distutils.misc_util.get_info('npymath')# >>> npymath_info # doctest: +SKIP# {'define_macros': [], 'libraries': ['npymath'], 'library_dirs': ['.../numpy/_core/lib'], 'include_dirs': ['.../numpy/_core/include']}# 然后可以将这个 info 字典作为 `Configuration` 实例的输入:# config.add_extension('foo', sources=['foo.c'], extra_info=npymath_info)"""
# 从 numpy.distutils.npy_pkg_config 导入 parse_flags 函数
from numpy.distutils.npy_pkg_config import parse_flags
# 使用 get_pkg_info 函数获取指定包的信息,并将结果存储在 pkg_info 中
pkg_info = get_pkg_info(pkgname, dirs)
# 将 LibraryInfo 实例解析为 build_info 字典
info = parse_flags(pkg_info.cflags())
# 遍历解析 pkg_info.libs() 的结果,将其项逐一添加到 info 字典的对应键中
for k, v in parse_flags(pkg_info.libs()).items():
info[k].extend(v)
# add_extension 函数的 extra_info 参数是 ANAL
# 将 info 字典中的 'macros' 键重命名为 'define_macros',并删除 'macros' 和 'ignored' 键
info['define_macros'] = info['macros']
del info['macros']
del info['ignored']
# 返回构建信息字典
return info
def is_bootstrapping():
# 导入内置模块 builtins
import builtins
try:
# 检查 builtins 中是否定义了 __NUMPY_SETUP__ 属性
builtins.__NUMPY_SETUP__
# 如果存在该属性,则返回 True,表示正在引导设置
return True
except AttributeError:
# 如果不存在该属性,则返回 False,表示不是引导设置状态
return False
#########################
def default_config_dict(name = None, parent_name = None, local_path=None):
"""Return a configuration dictionary for usage in
configuration() function defined infile setup_<name>.py."""
# 导入警告模块
import warnings
# 发出警告,提醒使用新的配置方式替代过时的函数
warnings.warn('Use Configuration(%r,%r,top_path=%r) instead of '\
'deprecated default_config_dict(%r,%r,%r)'
% (name, parent_name, local_path,
name, parent_name, local_path,
), stacklevel=2)
# 创建 Configuration 对象 c,并返回其字典表示
c = Configuration(name, parent_name, local_path)
return c.todict()
def dict_append(d, **kws):
# 遍历关键字参数的字典 kws
for k, v in kws.items():
# 如果字典 d 中已存在键 k
if k in d:
# 获取原来的值 ov
ov = d[k]
# 如果原来的值 ov 是字符串类型,则直接替换为新值 v
if isinstance(ov, str):
d[k] = v
else:
# 如果原来的值 ov 是列表类型,则扩展新值 v
d[k].extend(v)
else:
# 如果字典 d 中不存在键 k,则直接赋值新值 v
d[k] = v
def appendpath(prefix, path):
# 如果操作系统路径分隔符不是 '/',则替换为当前系统的路径分隔符
if os.path.sep != '/':
prefix = prefix.replace('/', os.path.sep)
path = path.replace('/', os.path.sep)
# 初始化驱动器为空字符串
drive = ''
# 如果路径是绝对路径
if os.path.isabs(path):
# 获取前缀的驱动器部分
drive = os.path.splitdrive(prefix)[0]
# 获取前缀的绝对路径部分
absprefix = os.path.splitdrive(os.path.abspath(prefix))[1]
# 获取路径的驱动器部分和路径部分
pathdrive, path = os.path.splitdrive(path)
# 获取前缀绝对路径和路径的最长公共前缀
d = os.path.commonprefix([absprefix, path])
# 如果拼接后的前缀不等于原前缀或者拼接后的路径不等于原路径,则处理无效路径
if os.path.join(absprefix[:len(d)], absprefix[len(d):]) != absprefix \
or os.path.join(path[:len(d)], path[len(d):]) != path:
# 获取无效路径的父目录
d = os.path.dirname(d)
# 获取子路径
subpath = path[len(d):]
# 如果子路径是绝对路径,则去掉开头的斜杠
if os.path.isabs(subpath):
subpath = subpath[1:]
else:
# 如果路径不是绝对路径,则直接作为子路径
subpath = path
# 返回规范化的路径
return os.path.normpath(njoin(drive + prefix, subpath))
def generate_config_py(target):
"""Generate config.py file containing system_info information
used during building the package.
Usage:
config['py_modules'].append((packagename,'__config__',generate_config_py))"""
# 导入需要的模块
from numpy.distutils.system_info import system_info
from distutils.dir_util import mkpath
# 创建目标路径的父目录
mkpath(os.path.dirname(target))
# 返回目标路径
return target
def msvc_version(compiler):
"""Return version major and minor of compiler instance if it is
MSVC,raise an exception otherwise."""
# 如果编译器不是 MSVC,则抛出异常ifnot compiler.compiler_type =="msvc":raise ValueError("Compiler instance is not msvc (%s)"\
% compiler.compiler_type)# 返回 MSVC 编译器的主要版本和次要版本return compiler._MSVCCompiler__version
defget_build_architecture():# 在非 Windows 系统上导入 distutils.msvccompiler 会触发警告,因此延迟导入到此处from distutils.msvccompiler import get_build_architecture
# 返回构建的架构信息return get_build_architecture()
_cxx_ignore_flags ={'-Werror=implicit-function-declaration','-std=c99'}defsanitize_cxx_flags(cxxflags):'''
Some flags are valid for C but not C++. Prune them.
'''# 移除对 C++ 无效的标志# 暂无具体实现# 返回一个列表,其中包含在 cxxflags 中但不在 _cxx_ignore_flags 中的所有标志return[flag for flag in cxxflags if flag notin _cxx_ignore_flags]# 使用 importlib 工具来从指定位置的文件中导入模块 `modname`defexec_mod_from_location(modname, modfile):# 根据文件路径和模块名创建一个模块规范对象
spec = importlib.util.spec_from_file_location(modname, modfile)# 根据模块规范对象创建一个新的模块对象
foo = importlib.util.module_from_spec(spec)# 使用加载器执行模块的代码,将其加入到系统模块列表中
spec.loader.exec_module(foo)# 返回执行后的模块对象return foo