NumPy-源码解析-五-

83 阅读29分钟

NumPy 源码解析(五)

.\numpy\numpy\distutils\fcompiler\__init__.py

"""
Contains FCompiler, an abstract base class that defines the interface
for the numpy.distutils Fortran compiler abstraction model.

Terminology:

To be consistent, where the term 'executable' is used, it means the single
file, like 'gcc', that is executed, and should be a string. In contrast,
'command' means the entire command line, like ['gcc', '-c', 'file.c'], and
should be a list.

But note that FCompiler.executables is actually a dictionary of commands.

"""

__all__ = ['FCompiler', 'new_fcompiler', 'show_fcompilers',
           'dummy_fortran_file']

import os                         # 导入操作系统相关的功能
import sys                        # 导入系统相关的功能
import re                         # 导入正则表达式模块
from pathlib import Path          # 导入路径处理模块 Path

from distutils.sysconfig import get_python_lib   # 导入获取 Python 库路径的函数
from distutils.fancy_getopt import FancyGetopt   # 导入自定义命令行选项处理模块
from distutils.errors import DistutilsModuleError, \
     DistutilsExecError, CompileError, LinkError, DistutilsPlatformError   # 导入 distutils 的各种错误类
from distutils.util import split_quoted, strtobool   # 导入辅助函数 split_quoted 和 strtobool

from numpy.distutils.ccompiler import CCompiler, gen_lib_options   # 导入 CCompiler 类和 gen_lib_options 函数
from numpy.distutils import log   # 导入 numpy.distutils 中的日志模块
from numpy.distutils.misc_util import is_string, all_strings, is_sequence, \
    make_temp_file, get_shared_lib_extension   # 导入一些辅助函数和工具函数
from numpy.distutils.exec_command import find_executable   # 导入查找可执行文件的函数
from numpy.distutils import _shell_utils   # 导入私有模块 _shell_utils

from .environment import EnvironmentConfig   # 导入环境配置相关的模块

__metaclass__ = type   # 使用 Python 2 风格的类定义元类

FORTRAN_COMMON_FIXED_EXTENSIONS = ['.for', '.ftn', '.f77', '.f']   # 常见的 Fortran 文件扩展名列表

class CompilerNotFound(Exception):
    pass

def flaglist(s):
    if is_string(s):   # 如果参数是字符串
        return split_quoted(s)   # 调用 split_quoted 函数分割字符串返回列表
    else:
        return s   # 否则直接返回参数本身

def str2bool(s):
    if is_string(s):   # 如果参数是字符串
        return strtobool(s)   # 调用 strtobool 函数转换为布尔值并返回
    return bool(s)   # 否则直接转换为布尔值并返回

def is_sequence_of_strings(seq):
    return is_sequence(seq) and all_strings(seq)   # 判断是否为字符串序列的函数

class FCompiler(CCompiler):
    """Abstract base class to define the interface that must be implemented
    by real Fortran compiler classes.

    Methods that subclasses may redefine:

        update_executables(), find_executables(), get_version()
        get_flags(), get_flags_opt(), get_flags_arch(), get_flags_debug()
        get_flags_f77(), get_flags_opt_f77(), get_flags_arch_f77(),
        get_flags_debug_f77(), get_flags_f90(), get_flags_opt_f90(),
        get_flags_arch_f90(), get_flags_debug_f90(),
        get_flags_fix(), get_flags_linker_so()

    DON'T call these methods (except get_version) after
    constructing a compiler instance or inside any other method.
    All methods, except update_executables() and find_executables(),
    may call the get_version() method.

    After constructing a compiler instance, always call customize(dist=None)
    method that finalizes compiler construction and makes the following
    attributes available:
      compiler_f77
      compiler_f90
      compiler_fix
      linker_so
      archiver
      ranlib
      libraries
      library_dirs
    """
    
    # These are the environment variables and distutils keys used.
    # Each configuration description is
    # (<hook name>, <environment variable>, <key in distutils.cfg>, <convert>, <append>)
    # The hook names are handled by the self._environment_hook method.
    # - names starting with 'self.' call methods in this class
    # - names starting with 'exe.' return the key in the executables dict
    # - names like 'flags.YYY' return self.get_flag_YYY()
    # convert is either None or a function to convert a string to the
    # appropriate type used.
    
    # 定义distutils_vars变量,用于存储与环境配置相关的选项
    distutils_vars = EnvironmentConfig(
        distutils_section='config_fc',
        noopt = (None, None, 'noopt', str2bool, False),  # 无优化选项,使用str2bool函数将字符串转换为布尔值
        noarch = (None, None, 'noarch', str2bool, False),  # 非架构相关选项,使用str2bool函数将字符串转换为布尔值
        debug = (None, None, 'debug', str2bool, False),  # 调试选项,使用str2bool函数将字符串转换为布尔值
        verbose = (None, None, 'verbose', str2bool, False),  # 详细模式选项,使用str2bool函数将字符串转换为布尔值
    )
    
    # 定义command_vars变量,用于存储编译器和链接器等命令相关的选项
    command_vars = EnvironmentConfig(
        distutils_section='config_fc',
        compiler_f77 = ('exe.compiler_f77', 'F77', 'f77exec', None, False),  # Fortran 77编译器选项
        compiler_f90 = ('exe.compiler_f90', 'F90', 'f90exec', None, False),  # Fortran 90编译器选项
        compiler_fix = ('exe.compiler_fix', 'F90', 'f90exec', None, False),  # 修复编译器选项
        version_cmd = ('exe.version_cmd', None, None, None, False),  # 版本命令选项
        linker_so = ('exe.linker_so', 'LDSHARED', 'ldshared', None, False),  # 共享目标文件链接器选项
        linker_exe = ('exe.linker_exe', 'LD', 'ld', None, False),  # 可执行文件链接器选项
        archiver = (None, 'AR', 'ar', None, False),  # 静态库归档工具选项
        ranlib = (None, 'RANLIB', 'ranlib', None, False),  # ranlib命令选项
    )
    
    # 定义flag_vars变量,用于存储编译和链接标志相关的选项
    flag_vars = EnvironmentConfig(
        distutils_section='config_fc',
        f77 = ('flags.f77', 'F77FLAGS', 'f77flags', flaglist, True),  # Fortran 77编译标志选项,使用flaglist处理标志列表
        f90 = ('flags.f90', 'F90FLAGS', 'f90flags', flaglist, True),  # Fortran 90编译标志选项,使用flaglist处理标志列表
        free = ('flags.free', 'FREEFLAGS', 'freeflags', flaglist, True),  # 自由格式编译标志选项,使用flaglist处理标志列表
        fix = ('flags.fix', None, None, flaglist, False),  # 修复格式编译标志选项,使用flaglist处理标志列表
        opt = ('flags.opt', 'FOPT', 'opt', flaglist, True),  # 优化编译标志选项,使用flaglist处理标志列表
        opt_f77 = ('flags.opt_f77', None, None, flaglist, False),  # Fortran 77优化编译标志选项,使用flaglist处理标志列表
        opt_f90 = ('flags.opt_f90', None, None, flaglist, False),  # Fortran 90优化编译标志选项,使用flaglist处理标志列表
        arch = ('flags.arch', 'FARCH', 'arch', flaglist, False),  # 架构编译标志选项,使用flaglist处理标志列表
        arch_f77 = ('flags.arch_f77', None, None, flaglist, False),  # Fortran 77架构编译标志选项,使用flaglist处理标志列表
        arch_f90 = ('flags.arch_f90', None, None, flaglist, False),  # Fortran 90架构编译标志选项,使用flaglist处理标志列表
        debug = ('flags.debug', 'FDEBUG', 'fdebug', flaglist, True),  # 调试编译标志选项,使用flaglist处理标志列表
        debug_f77 = ('flags.debug_f77', None, None, flaglist, False),  # Fortran 77调试编译标志选项,使用flaglist处理标志列表
        debug_f90 = ('flags.debug_f90', None, None, flaglist, False),  # Fortran 90调试编译标志选项,使用flaglist处理标志列表
        flags = ('self.get_flags', 'FFLAGS', 'fflags', flaglist, True),  # 获取所有标志选项的方法,使用flaglist处理标志列表
        linker_so = ('flags.linker_so', 'LDFLAGS', 'ldflags', flaglist, True),  # 共享目标文件链接器标志选项,使用flaglist处理标志列表
        linker_exe = ('flags.linker_exe', 'LDFLAGS', 'ldflags', flaglist, True),  # 可执行文件链接器标志选项,使用flaglist处理标志列表
        ar = ('flags.ar', 'ARFLAGS', 'arflags', flaglist, True),  # 静态库归档工具标志选项,使用flaglist处理标志列表
    )
    
    # 定义language_map变量,用于存储文件扩展名与编程语言的映射关系
    language_map = {'.f': 'f77',
                    '.for': 'f77',
                    '.F': 'f77',    # 需要预处理器的Fortran 77文件
                    '.ftn': 'f77',
                    '.f77': 'f77',
                    '.f90': 'f90',
                    '.F90': 'f90',  # 需要预处理器的Fortran 90文件
                    '.f95': 'f90',
                    }
    
    # 定义language_order变量,用于存储编译优先顺序的列表
    language_order = ['f90', 'f77']
    
    # 这些变量将由子类设置
    # 初始化变量,声明编译器类型、编译器别名为空,版本模式为空
    compiler_type = None
    compiler_aliases = ()
    version_pattern = None

    # 初始化空列表和字典,包含预定义的执行文件命令列表
    possible_executables = []
    executables = {
        'version_cmd': ["f77", "-v"],     # 版本命令,调用 f77 编译器获取版本信息
        'compiler_f77': ["f77"],          # f77 编译器命令
        'compiler_f90': ["f90"],          # f90 编译器命令
        'compiler_fix': ["f90", "-fixed"],# f90 编译器以固定格式编译
        'linker_so': ["f90", "-shared"],  # f90 编译器链接共享库
        'linker_exe': ["f90"],            # f90 编译器链接可执行文件
        'archiver': ["ar", "-cr"],        # 静态库打包命令 ar -cr
        'ranlib': None,                   # ranlib 命令未定义
        }

    # 如果编译器不支持编译 Fortran 90,则建议使用另一个编译器类型
    suggested_f90_compiler = None

    compile_switch = "-c"                 # 编译开关
    object_switch = "-o "                 # 对象文件生成开关,结尾空格影响字符串拼接
                                         # 如果缺少,则通过字符串连接添加到对象文件名前

    library_switch = "-o "                # 库文件生成开关,同上
                                         # 用于指定模块文件的创建和搜索位置的开关
    module_dir_switch = None              # 模块目录开关,未定义

    module_include_switch = '-I'          # 模块包含路径开关,用于指定模块文件的搜索位置

    pic_flags = []                        # 用于创建位置无关代码的标志

    src_extensions = ['.for', '.ftn', '.f77', '.f', '.f90', '.f95', '.F', '.F90', '.FOR']  # 源文件扩展名列表
    obj_extension = ".o"                  # 对象文件扩展名

    shared_lib_extension = get_shared_lib_extension()  # 共享库文件扩展名,通过函数获取
    static_lib_extension = ".a"           # 静态库文件扩展名,或者 .lib
    static_lib_format = "lib%s%s"         # 静态库格式

    shared_lib_format = "%s%s"            # 共享库格式
    exe_extension = ""                    # 可执行文件扩展名为空

    _exe_cache = {}                       # 可执行文件缓存

    _executable_keys = ['version_cmd', 'compiler_f77', 'compiler_f90',
                        'compiler_fix', 'linker_so', 'linker_exe', 'archiver',
                        'ranlib']

    # new_fcompiler 调用时会被设置,在 command/{build_ext.py, build_clib.py, config.py} 文件中
    c_compiler = None                     # C 编译器未定义

    # extra_{f77,f90}_compile_args 由 build_ext.build_extension 方法设置
    extra_f77_compile_args = []           # 额外的 f77 编译参数
    extra_f90_compile_args = []           # 额外的 f90 编译参数

    def __init__(self, *args, **kw):
        # 调用父类 CCompiler 的初始化方法,并克隆环境钩子以配置 Distutils 变量
        CCompiler.__init__(self, *args, **kw)
        self.distutils_vars = self.distutils_vars.clone(self._environment_hook)
        self.command_vars = self.command_vars.clone(self._environment_hook)
        self.flag_vars = self.flag_vars.clone(self._environment_hook)
        self.executables = self.executables.copy()

        # 将预定义的执行文件键复制到 executables 字典中,如果不存在则设为 None
        for e in self._executable_keys:
            if e not in self.executables:
                self.executables[e] = None

        # 用于跟踪 customize() 方法是否已调用
        self._is_customised = False
    # 实现对象的浅复制操作
    def __copy__(self):
        # 创建一个新的对象,类型与当前对象相同
        obj = self.__new__(self.__class__)
        # 将当前对象的属性复制到新对象中
        obj.__dict__.update(self.__dict__)
        # 使用环境钩子克隆 distutils_vars,command_vars,flag_vars 对象
        obj.distutils_vars = obj.distutils_vars.clone(obj._environment_hook)
        obj.command_vars = obj.command_vars.clone(obj._environment_hook)
        obj.flag_vars = obj.flag_vars.clone(obj._environment_hook)
        # 复制 executables 字典
        obj.executables = obj.executables.copy()
        return obj

    # 返回对象的浅复制
    def copy(self):
        return self.__copy__()

    # 使用属性来访问 CCompiler 使用的属性。直接从 self.executables 字典中设置这些属性可能会出错,
    # 因此每次都从中获取它们。
    def _command_property(key):
        def fget(self):
            # 断言对象已经定制化
            assert self._is_customised
            # 返回指定 key 对应的可执行文件路径
            return self.executables[key]
        return property(fget=fget)
    # 定义各个命令属性
    version_cmd = _command_property('version_cmd')
    compiler_f77 = _command_property('compiler_f77')
    compiler_f90 = _command_property('compiler_f90')
    compiler_fix = _command_property('compiler_fix')
    linker_so = _command_property('linker_so')
    linker_exe = _command_property('linker_exe')
    archiver = _command_property('archiver')
    ranlib = _command_property('ranlib')

    # 使术语一致化。
    def set_executable(self, key, value):
        self.set_command(key, value)

    # 设置多个命令的执行路径。
    def set_commands(self, **kw):
        for k, v in kw.items():
            self.set_command(k, v)

    # 设置单个命令的执行路径。
    def set_command(self, key, value):
        # 如果 key 不在可执行文件关键字列表中,抛出 ValueError 异常
        if not key in self._executable_keys:
            raise ValueError(
                "unknown executable '%s' for class %s" %
                (key, self.__class__.__name__))
        # 如果 value 是字符串类型,将其拆分为列表
        if is_string(value):
            value = split_quoted(value)
        # 断言 value 为 None 或者是字符串列表类型
        assert value is None or is_sequence_of_strings(value[1:]), (key, value)
        # 设置可执行文件字典中指定 key 的值为 value
        self.executables[key] = value

    ######################################################################
    ## Methods that subclasses may redefine. But don't call these methods!
    ## They are private to FCompiler class and may return unexpected
    ## results if used elsewhere. So, you have been warned..

    # 更新可执行文件字典。子类可以重新定义此方法。
    def update_executables(self):
        """Called at the beginning of customisation. Subclasses should
        override this if they need to set up the executables dictionary.

        Note that self.find_executables() is run afterwards, so the
        self.executables dictionary values can contain <F77> or <F90> as
        the command, which will be replaced by the found F77 or F90
        compiler.
        """
        pass

    # 获取通用编译器标志列表。
    def get_flags(self):
        """List of flags common to all compiler types."""
        return [] + self.pic_flags

    # 获取特定于 Fortran 77 的编译器标志列表。
    def _get_command_flags(self, key):
        # 获取指定 key 对应的命令
        cmd = self.executables.get(key, None)
        if cmd is None:
            return []
        # 返回命令的标志列表(去掉第一个元素)
        return cmd[1:]

    # 获取特定于 Fortran 77 的编译器标志列表。
    def get_flags_f77(self):
        """List of Fortran 77 specific flags."""
        return self._get_command_flags('compiler_f77')
    # 返回 Fortran 90 特定编译器标志列表
    def get_flags_f90(self):
        """List of Fortran 90 specific flags."""
        return self._get_command_flags('compiler_f90')

    # 返回 Fortran 90 自由格式特定编译器标志列表(空列表)
    def get_flags_free(self):
        """List of Fortran 90 free format specific flags."""
        return []

    # 返回 Fortran 90 定格式特定编译器标志列表
    def get_flags_fix(self):
        """List of Fortran 90 fixed format specific flags."""
        return self._get_command_flags('compiler_fix')

    # 返回用于构建共享库的链接器标志列表
    def get_flags_linker_so(self):
        """List of linker flags to build a shared library."""
        return self._get_command_flags('linker_so')

    # 返回用于构建可执行文件的链接器标志列表
    def get_flags_linker_exe(self):
        """List of linker flags to build an executable."""
        return self._get_command_flags('linker_exe')

    # 返回归档器标志列表
    def get_flags_ar(self):
        """List of archiver flags. """
        return self._get_command_flags('archiver')

    # 返回体系结构独立的编译器标志列表(空列表)
    def get_flags_opt(self):
        """List of architecture independent compiler flags."""
        return []

    # 返回体系结构相关的编译器标志列表(空列表)
    def get_flags_arch(self):
        """List of architecture dependent compiler flags."""
        return []

    # 返回用于带调试信息编译的编译器标志列表(空列表)
    def get_flags_debug(self):
        """List of compiler flags to compile with debugging information."""
        return []

    # 设置 Fortran 77 和 Fortran 90 版本的体系结构无关编译器标志相同
    get_flags_opt_f77 = get_flags_opt_f90 = get_flags_opt

    # 设置 Fortran 77 和 Fortran 90 版本的体系结构相关编译器标志相同
    get_flags_arch_f77 = get_flags_arch_f90 = get_flags_arch

    # 设置 Fortran 77 和 Fortran 90 版本的调试编译器标志相同
    get_flags_debug_f77 = get_flags_debug_f90 = get_flags_debug

    # 返回编译器库列表
    def get_libraries(self):
        """List of compiler libraries."""
        return self.libraries[:]

    # 返回编译器库目录列表
    def get_library_dirs(self):
        """List of compiler library directories."""
        return self.library_dirs[:]

    # 获取编译器版本信息,如果未找到则引发 CompilerNotFound 异常
    def get_version(self, force=False, ok_status=[0]):
        assert self._is_customised
        version = CCompiler.get_version(self, force=force, ok_status=ok_status)
        if version is None:
            raise CompilerNotFound()
        return version

    ############################################################

    ## Public methods:

    # 打印编译器实例的属性列表
    def dump_properties(self):
        """Print out the attributes of a compiler instance."""
        props = []
        # 收集要打印的属性名
        for key in list(self.executables.keys()) + \
                ['version', 'libraries', 'library_dirs',
                 'object_switch', 'compile_switch']:
            if hasattr(self, key):
                v = getattr(self, key)
                props.append((key, None, '= '+repr(v)))
        props.sort()

        # 使用 FancyGetopt 格式化输出属性信息
        pretty_printer = FancyGetopt(props)
        for l in pretty_printer.generate_help("%s instance properties:" \
                                              % (self.__class__.__name__)):
            if l[:4]=='  --':
                l = '  ' + l[4:]
            print(l)

    ###################
    def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts):
        """Compile 'src' to produce 'obj'."""
        # 初始化一个空字典来存储源文件的特定编译选项
        src_flags = {}
        
        # 检查源文件的后缀名是否在FORTRAN_COMMON_FIXED_EXTENSIONS列表中,并且不含有f90头部信息
        if Path(src).suffix.lower() in FORTRAN_COMMON_FIXED_EXTENSIONS \
           and not has_f90_header(src):
            # 设置为Fortran 77风格
            flavor = ':f77'
            # 使用Fortran 77编译器
            compiler = self.compiler_f77
            # 获取Fortran 77源文件的编译选项
            src_flags = get_f77flags(src)
            # 获取额外的Fortran 77编译选项,如果没有则为空列表
            extra_compile_args = self.extra_f77_compile_args or []
        
        # 如果源文件是自由格式的Fortran 90
        elif is_free_format(src):
            # 设置为Fortran 90风格
            flavor = ':f90'
            # 使用Fortran 90编译器,如果没有则引发异常
            compiler = self.compiler_f90
            if compiler is None:
                raise DistutilsExecError('f90 not supported by %s needed for %s'\
                      % (self.__class__.__name__, src))
            # 获取额外的Fortran 90编译选项,如果没有则为空列表
            extra_compile_args = self.extra_f90_compile_args or []
        
        # 默认情况下,使用固定格式Fortran编译器
        else:
            # 设置为Fortran固定格式风格
            flavor = ':fix'
            # 使用Fortran固定格式编译器,如果没有则引发异常
            compiler = self.compiler_fix
            if compiler is None:
                raise DistutilsExecError('f90 (fixed) not supported by %s needed for %s'\
                      % (self.__class__.__name__, src))
            # 获取额外的Fortran固定格式编译选项,如果没有则为空列表
            extra_compile_args = self.extra_f90_compile_args or []
        
        # 根据self.object_switch的值选择正确的目标文件参数列表
        if self.object_switch[-1]==' ':
            o_args = [self.object_switch.strip(), obj]
        else:
            o_args = [self.object_switch.strip()+obj]
        
        # 确保self.compile_switch不为空,并设置源文件参数列表
        assert self.compile_switch.strip()
        s_args = [self.compile_switch, src]
        
        # 如果存在额外的编译选项,则记录日志
        if extra_compile_args:
            log.info('extra %s options: %r' \
                     % (flavor[1:], ' '.join(extra_compile_args)))
        
        # 获取源文件特定编译选项
        extra_flags = src_flags.get(self.compiler_type, [])
        # 如果存在特定编译选项,则记录日志
        if extra_flags:
            log.info('using compile options from source: %r' \
                     % ' '.join(extra_flags))
        
        # 构建编译命令
        command = compiler + cc_args + extra_flags + s_args + o_args \
                  + extra_postargs + extra_compile_args
        
        # 设置显示信息,包含编译器和源文件名称
        display = '%s: %s' % (os.path.basename(compiler[0]) + flavor,
                              src)
        try:
            # 执行编译命令
            self.spawn(command, display=display)
        except DistutilsExecError as e:
            # 如果发生执行错误,则捕获并重新抛出CompileError异常
            msg = str(e)
            raise CompileError(msg) from None
    # 返回模块选项列表,包括模块目录开关和模块构建目录
    def module_options(self, module_dirs, module_build_dir):
        options = []
        # 检查是否存在模块目录开关
        if self.module_dir_switch is not None:
            # 如果开关的最后一个字符是空格,则扩展选项列表以包括修剪后的开关和模块构建目录
            if self.module_dir_switch[-1]==' ':
                options.extend([self.module_dir_switch.strip(), module_build_dir])
            else:
                # 否则将开关和模块构建目录组合成一个选项并添加到列表中
                options.append(self.module_dir_switch.strip()+module_build_dir)
        else:
            # 如果没有模块目录开关,打印警告信息并指出忽略了模块构建目录选项
            print('XXX: module_build_dir=%r option ignored' % (module_build_dir))
            print('XXX: Fix module_dir_switch for ', self.__class__.__name__)
        # 检查是否存在模块包含目录开关
        if self.module_include_switch is not None:
            # 对于每个模块构建目录和每个模块目录,构建包含目录选项并添加到列表中
            for d in [module_build_dir]+module_dirs:
                options.append('%s%s' % (self.module_include_switch, d))
        else:
            # 如果没有模块包含目录开关,打印警告信息并指出忽略了模块目录选项
            print('XXX: module_dirs=%r option ignored' % (module_dirs))
            print('XXX: Fix module_include_switch for ', self.__class__.__name__)
        # 返回最终的选项列表
        return options

    # 返回链接库选项,以"-l"开头
    def library_option(self, lib):
        return "-l" + lib
    
    # 返回链接库目录选项,以"-L"开头
    def library_dir_option(self, dir):
        return "-L" + dir
    # 将传入的 objects 和 output_dir 参数规范化处理,确保它们符合要求
    objects, output_dir = self._fix_object_args(objects, output_dir)
    
    # 将传入的 libraries、library_dirs 和 runtime_library_dirs 参数规范化处理,确保它们符合要求
    libraries, library_dirs, runtime_library_dirs = \
        self._fix_lib_args(libraries, library_dirs, runtime_library_dirs)
    
    # 根据规范化后的参数生成库选项
    lib_opts = gen_lib_options(self, library_dirs, runtime_library_dirs,
                               libraries)
    
    # 如果 output_dir 是字符串类型,则将 output_filename 放置在 output_dir 中
    if is_string(output_dir):
        output_filename = os.path.join(output_dir, output_filename)
    # 如果 output_dir 不为 None 且不是字符串类型,则抛出类型错误异常
    elif output_dir is not None:
        raise TypeError("'output_dir' must be a string or None")
    
    # 如果需要进行链接操作,则执行以下步骤
    if self._need_link(objects, output_filename):
        # 根据 self.library_switch 的最后一个字符判断需要执行的操作
        if self.library_switch[-1]==' ':
            o_args = [self.library_switch.strip(), output_filename]
        else:
            o_args = [self.library_switch.strip()+output_filename]
        
        # 如果 self.objects 是字符串类型,则将其与 objects 合并
        if is_string(self.objects):
            ld_args = objects + [self.objects]
        else:
            ld_args = objects + self.objects
        
        # 将 ld_args 与 lib_opts 和 o_args 合并
        ld_args = ld_args + lib_opts + o_args
        
        # 如果 debug 标志为真,则在 ld_args 的开头加上 '-g'
        if debug:
            ld_args[:0] = ['-g']
        
        # 如果 extra_preargs 存在,则将其加入到 ld_args 的开头
        if extra_preargs:
            ld_args[:0] = extra_preargs
        
        # 如果 extra_postargs 存在,则将其添加到 ld_args 的末尾
        if extra_postargs:
            ld_args.extend(extra_postargs)
        
        # 确保输出文件的目录存在,如果不存在则创建
        self.mkpath(os.path.dirname(output_filename))
        
        # 根据 target_desc 类型选择相应的链接器
        if target_desc == CCompiler.EXECUTABLE:
            linker = self.linker_exe[:]
        else:
            linker = self.linker_so[:]
        
        # 组合链接器和 ld_args 形成完整的命令
        command = linker + ld_args
        
        # 尝试执行链接命令
        try:
            self.spawn(command)
        except DistutilsExecError as e:
            # 如果发生 DistutilsExecError 异常,则将其转换为 LinkError 异常并抛出
            msg = str(e)
            raise LinkError(msg) from None
    else:
        # 如果不需要进行链接操作,则记录调试信息,表明跳过此文件(因为已经是最新的)
        log.debug("skipping %s (up-to-date)", output_filename)
    
# 根据给定的 hook_name,执行与环境相关的钩子操作,返回相应的结果
def _environment_hook(self, name, hook_name):
    if hook_name is None:
        return None
    if is_string(hook_name):
        # 如果 hook_name 以 'self.' 开头,则从当前对象中获取对应方法并执行
        if hook_name.startswith('self.'):
            hook_name = hook_name[5:]
            hook = getattr(self, hook_name)
            return hook()
        # 如果 hook_name 以 'exe.' 开头,则从 executables 属性中获取对应的变量值
        elif hook_name.startswith('exe.'):
            hook_name = hook_name[4:]
            var = self.executables[hook_name]
            if var:
                return var[0]
            else:
                return None
        # 如果 hook_name 以 'flags.' 开头,则调用相应的 get_flags_ 方法获取标志
        elif hook_name.startswith('flags.'):
            hook_name = hook_name[6:]
            hook = getattr(self, 'get_flags_' + hook_name)
            return hook()
    else:
        # 如果 hook_name 不是字符串类型,则直接返回它
        return hook_name()

# 检查给定的 C 编译器是否能够链接当前编译器生成的对象
def can_ccompiler_link(self, ccompiler):
    """
    检查给定的 C 编译器是否能够链接当前编译器生成的对象。
    """
    return True
    # 定义一个方法,用于包装无法与默认链接器兼容的对象文件,使其兼容
    def wrap_unlinkable_objects(self, objects, output_dir, extra_dll_dir):
        """
        Convert a set of object files that are not compatible with the default
        linker, to a file that is compatible.
    
        Parameters
        ----------
        objects : list
            List of object files to include.
        output_dir : str
            Output directory to place generated object files.
        extra_dll_dir : str
            Output directory to place extra DLL files that need to be
            included on Windows.
    
        Returns
        -------
        converted_objects : list of str
             List of converted object files.
             Note that the number of output files is not necessarily
             the same as inputs.
    
        """
        # 抛出一个未实现错误,表示该方法需要在子类中实现
        raise NotImplementedError()
    
    ## class FCompiler
# 默认的编译器配置,根据不同的操作系统平台和名称映射到对应的编译器列表
_default_compilers = (
    # 对应 win32 平台的编译器列表
    ('win32', ('gnu', 'intelv', 'absoft', 'compaqv', 'intelev', 'gnu95', 'g95',
               'intelvem', 'intelem', 'flang')),
    # 对应 cygwin.* 平台的编译器列表
    ('cygwin.*', ('gnu', 'intelv', 'absoft', 'compaqv', 'intelev', 'gnu95', 'g95')),
    # 对应 linux.* 平台的编译器列表
    ('linux.*', ('arm', 'gnu95', 'intel', 'lahey', 'pg', 'nv', 'absoft', 'nag',
                 'vast', 'compaq', 'intele', 'intelem', 'gnu', 'g95', 
                 'pathf95', 'nagfor', 'fujitsu')),
    # 对应 darwin.* 平台的编译器列表
    ('darwin.*', ('gnu95', 'nag', 'nagfor', 'absoft', 'ibm', 'intel', 'gnu',
                 'g95', 'pg')),
    # 对应 sunos.* 平台的编译器列表
    ('sunos.*', ('sun', 'gnu', 'gnu95', 'g95')),
    # 对应 irix.* 平台的编译器列表
    ('irix.*', ('mips', 'gnu', 'gnu95',)),
    # 对应 aix.* 平台的编译器列表
    ('aix.*', ('ibm', 'gnu', 'gnu95',)),
    # 对应 posix 平台的编译器列表
    ('posix', ('gnu', 'gnu95',)),
    # 对应 nt 平台的编译器列表
    ('nt', ('gnu', 'gnu95',)),
    # 对应 mac 平台的编译器列表
    ('mac', ('gnu95', 'gnu', 'pg')),
)

# 默认情况下,未指定任何编译器类
fcompiler_class = None
# 默认情况下,未指定任何编译器别名
fcompiler_aliases = None

def load_all_fcompiler_classes():
    """缓存所有在 numpy.distutils.fcompiler 包中找到的 FCompiler 类。

    使用 glob 模块获取当前文件夹下所有的 Python 模块文件,并尝试导入这些模块,
    将其中定义的编译器类及其别名缓存起来。

    """
    from glob import glob
    global fcompiler_class, fcompiler_aliases
    # 如果已经缓存了编译器类信息,则直接返回,避免重复加载
    if fcompiler_class is not None:
        return
    # 获取当前文件所在文件夹下所有的 Python 模块文件路径
    pys = os.path.join(os.path.dirname(__file__), '*.py')
    # 初始化编译器类字典和别名字典
    fcompiler_class = {}
    fcompiler_aliases = {}
    # 遍历所有匹配的模块文件路径
    for fname in glob(pys):
        # 提取模块文件名(不包含扩展名)
        module_name, ext = os.path.splitext(os.path.basename(fname))
        # 构建完整的模块名称
        module_name = 'numpy.distutils.fcompiler.' + module_name
        # 动态导入模块
        __import__(module_name)
        # 获取导入的模块对象
        module = sys.modules[module_name]
        # 检查模块是否定义了编译器列表
        if hasattr(module, 'compilers'):
            # 遍历模块中定义的编译器类名
            for cname in module.compilers:
                # 获取编译器类对象
                klass = getattr(module, cname)
                # 构建编译器描述信息
                desc = (klass.compiler_type, klass, klass.description)
                # 将编译器类型及其类对象和描述信息添加到编译器类字典中
                fcompiler_class[klass.compiler_type] = desc
                # 遍历编译器类定义的别名列表
                for alias in klass.compiler_aliases:
                    # 检查别名是否已经存在于别名字典中,避免重复定义
                    if alias in fcompiler_aliases:
                        raise ValueError("alias %r defined for both %s and %s"
                                         % (alias, klass.__name__,
                                            fcompiler_aliases[alias][1].__name__))
                    # 将别名及其描述信息添加到别名字典中
                    fcompiler_aliases[alias] = desc

def _find_existing_fcompiler(compiler_types,
                             osname=None, platform=None,
                             requiref90=False,
                             c_compiler=None):
    """根据指定的条件查找现有的编译器。

    使用 numpy.distutils.core 模块中的 get_distribution 函数获取当前发行版信息,
    以确定编译器的查找范围。

    Args:
        compiler_types: 待查找的编译器类型列表。
        osname: 操作系统名称。
        platform: 平台名称。
        requiref90: 是否需要支持 Fortran 90 编译。
        c_compiler: C 编译器类型。

    """
    from numpy.distutils.core import get_distribution
    # 获取当前的发行版信息
    dist = get_distribution(always=True)
    # 遍历编译器类型列表,逐个尝试不同的编译器类型
    for compiler_type in compiler_types:
        # 初始化变量 v 为 None,用于存储编译器版本信息
        v = None
        try:
            # 创建新的编译器对象 c,传入平台和当前编译器类型
            c = new_fcompiler(plat=platform, compiler=compiler_type,
                              c_compiler=c_compiler)
            # 根据传入的配置信息对编译器对象 c 进行定制化设置
            c.customize(dist)
            # 获取当前编译器对象 c 的版本信息
            v = c.get_version()
            
            # 如果需要支持 Fortran 90,并且当前编译器不支持 Fortran 90
            if requiref90 and c.compiler_f90 is None:
                # 重置 v 为 None
                v = None
                # 获取推荐的 Fortran 90 编译器
                new_compiler = c.suggested_f90_compiler
                if new_compiler:
                    # 输出警告信息,尝试推荐的 Fortran 90 编译器
                    log.warn('Trying %r compiler as suggested by %r '
                             'compiler for f90 support.' % (compiler_type,
                                                            new_compiler))
                    # 使用推荐的 Fortran 90 编译器创建新的编译器对象 c
                    c = new_fcompiler(plat=platform, compiler=new_compiler,
                                      c_compiler=c_compiler)
                    # 对新的编译器对象 c 进行定制化设置
                    c.customize(dist)
                    # 获取新编译器对象的版本信息
                    v = c.get_version()
                    # 如果获取到版本信息,则更新编译器类型为新的编译器类型
                    if v is not None:
                        compiler_type = new_compiler
                        
            # 如果需要支持 Fortran 90 且当前编译器不支持,抛出异常
            if requiref90 and c.compiler_f90 is None:
                raise ValueError('%s does not support compiling f90 codes, '
                                 'skipping.' % (c.__class__.__name__))
                                 
        # 捕获 Distutils 模块错误异常
        except DistutilsModuleError:
            log.debug("_find_existing_fcompiler: compiler_type='%s' raised DistutilsModuleError", compiler_type)
            
        # 捕获编译器未找到异常
        except CompilerNotFound:
            log.debug("_find_existing_fcompiler: compiler_type='%s' not found", compiler_type)
            
        # 如果获取到版本信息 v,则返回当前编译器类型
        if v is not None:
            return compiler_type
    
    # 如果未找到适用的编译器类型,则返回 None
    return None
# 确定适用于特定操作系统和平台的可用 Fortran 编译器类型列表
def available_fcompilers_for_platform(osname=None, platform=None):
    if osname is None:
        osname = os.name
    if platform is None:
        platform = sys.platform
    matching_compiler_types = []
    for pattern, compiler_type in _default_compilers:
        # 如果操作系统或平台名称与给定模式匹配,则将对应的编译器类型添加到列表中
        if re.match(pattern, platform) or re.match(pattern, osname):
            for ct in compiler_type:
                if ct not in matching_compiler_types:
                    matching_compiler_types.append(ct)
    if not matching_compiler_types:
        matching_compiler_types.append('gnu')  # 如果没有匹配的编译器类型,则默认使用 'gnu'
    return matching_compiler_types

# 获取适合特定平台的默认 Fortran 编译器
def get_default_fcompiler(osname=None, platform=None, requiref90=False,
                          c_compiler=None):
    """Determine the default Fortran compiler to use for the given
    platform."""
    matching_compiler_types = available_fcompilers_for_platform(osname,
                                                                platform)
    # 记录匹配的编译器类型到日志
    log.info("get_default_fcompiler: matching types: '%s'",
             matching_compiler_types)
    # 根据匹配的编译器类型查找现有的 Fortran 编译器
    compiler_type =  _find_existing_fcompiler(matching_compiler_types,
                                              osname=osname,
                                              platform=platform,
                                              requiref90=requiref90,
                                              c_compiler=c_compiler)
    return compiler_type

# 用于避免每次重新检查 Fortran 编译器的标志集合
failed_fcompilers = set()

# 为给定的平台和编译器组合生成某个 FCompiler 子类的实例
def new_fcompiler(plat=None,
                  compiler=None,
                  verbose=0,
                  dry_run=0,
                  force=0,
                  requiref90=False,
                  c_compiler=None):
    """Generate an instance of some FCompiler subclass for the supplied
    platform/compiler combination.
    """
    global failed_fcompilers
    fcompiler_key = (plat, compiler)
    # 如果已经知道给定平台和编译器组合的编译失败,则返回 None
    if fcompiler_key in failed_fcompilers:
        return None

    # 加载所有的 Fortran 编译器类
    load_all_fcompiler_classes()
    if plat is None:
        plat = os.name
    if compiler is None:
        # 获取默认的 Fortran 编译器
        compiler = get_default_fcompiler(plat, requiref90=requiref90,
                                         c_compiler=c_compiler)
    # 根据编译器名称查找对应的模块名、类和详细描述
    if compiler in fcompiler_class:
        module_name, klass, long_description = fcompiler_class[compiler]
    elif compiler in fcompiler_aliases:
        module_name, klass, long_description = fcompiler_aliases[compiler]
    else:
        # 如果未知如何在特定平台上编译 Fortran 代码,则记录警告并添加到失败编译器集合中
        msg = "don't know how to compile Fortran code on platform '%s'" % plat
        if compiler is not None:
            msg = msg + " with '%s' compiler." % compiler
            msg = msg + " Supported compilers are: %s)" \
                  % (','.join(fcompiler_class.keys()))
        log.warn(msg)
        failed_fcompilers.add(fcompiler_key)
        return None

    # 创建编译器实例并返回
    compiler = klass(verbose=verbose, dry_run=dry_run, force=force)
    compiler.c_compiler = c_compiler
    return compiler

# 显示 Fortran 编译器信息
def show_fcompilers(dist=None):
    """Print list of available compilers (used by the "--help-fcompiler"
    option to "config_fc").
    """

    # 如果没有传入 dist 参数,则从 distutils.dist 模块导入 Distribution 类
    if dist is None:
        from distutils.dist import Distribution
        # 从 numpy.distutils.command.config_compiler 模块导入 config_fc 函数
        from numpy.distutils.command.config_compiler import config_fc
        # 创建 Distribution 对象
        dist = Distribution()
        # 设置 Distribution 对象的脚本名称为当前脚本的基本文件名
        dist.script_name = os.path.basename(sys.argv[0])
        # 设置 Distribution 对象的脚本参数,包括 'config_fc' 和后续的命令行参数
        dist.script_args = ['config_fc'] + sys.argv[1:]
        # 尝试从脚本参数中移除 '--help-fcompiler'
        try:
            dist.script_args.remove('--help-fcompiler')
        except ValueError:
            pass
        # 将 config_fc 函数设置为 Distribution 对象的 cmdclass 中的 'config_fc' 命令类
        dist.cmdclass['config_fc'] = config_fc
        # 解析配置文件
        dist.parse_config_files()
        # 解析命令行参数
        dist.parse_command_line()

    # 初始化三个空列表来存储编译器信息
    compilers = []
    compilers_na = []
    compilers_ni = []

    # 如果没有指定 fcompiler_class,则加载所有的编译器类
    if not fcompiler_class:
        load_all_fcompiler_classes()

    # 获取当前平台上可用的编译器列表
    platform_compilers = available_fcompilers_for_platform()

    # 遍历平台上可用的每个编译器
    for compiler in platform_compilers:
        v = None
        # 设置日志的详细程度为 -2
        log.set_verbosity(-2)
        try:
            # 创建一个新的编译器实例,并根据 dist 对象的详细程度设置是否显示详细信息
            c = new_fcompiler(compiler=compiler, verbose=dist.verbose)
            # 根据 dist 对象自定义编译器
            c.customize(dist)
            # 获取编译器的版本信息
            v = c.get_version()
        except (DistutilsModuleError, CompilerNotFound) as e:
            # 如果出现异常,记录未找到编译器的调试信息
            log.debug("show_fcompilers: %s not found" % (compiler,))
            log.debug(repr(e))

        # 根据获取到的版本信息将编译器信息添加到不同的列表中
        if v is None:
            compilers_na.append(("fcompiler="+compiler, None,
                              fcompiler_class[compiler][2]))
        else:
            # 打印编译器的属性信息
            c.dump_properties()
            compilers.append(("fcompiler="+compiler, None,
                              fcompiler_class[compiler][2] + ' (%s)' % v))

    # 找出当前平台上未安装的编译器,并添加到对应的列表中
    compilers_ni = list(set(fcompiler_class.keys()) - set(platform_compilers))
    compilers_ni = [("fcompiler="+fc, None, fcompiler_class[fc][2])
                    for fc in compilers_ni]

    # 对三个列表进行排序
    compilers.sort()
    compilers_na.sort()
    compilers_ni.sort()

    # 使用 FancyGetopt 类创建一个美观的打印对象,并打印不同列表中的编译器信息
    pretty_printer = FancyGetopt(compilers)
    pretty_printer.print_help("Fortran compilers found:")

    pretty_printer = FancyGetopt(compilers_na)
    pretty_printer.print_help("Compilers available for this "
                              "platform, but not found:")

    # 如果有不适用于当前平台的编译器,则打印这些编译器信息
    if compilers_ni:
        pretty_printer = FancyGetopt(compilers_ni)
        pretty_printer.print_help("Compilers not available on this platform:")

    # 打印一条消息,指示如何查看编译器详细信息
    print("For compiler details, run 'config_fc --verbose' setup command.")
# 创建一个临时的 Fortran 文件,写入简单的 subroutine 定义,并返回文件名
def dummy_fortran_file():
    fo, name = make_temp_file(suffix='.f')
    fo.write("      subroutine dummy()\n      end\n")
    fo.close()
    return name[:-2]

# 正则表达式,用于检测文件头部是否包含 Fortran 标记
_has_f_header = re.compile(r'-\*-\s*fortran\s*-\*-', re.I).search

# 正则表达式,用于检测文件头部是否包含 f90 标记
_has_f90_header = re.compile(r'-\*-\s*f90\s*-\*-', re.I).search

# 正则表达式,用于检测文件头部是否包含 fix 标记
_has_fix_header = re.compile(r'-\*-\s*fix\s*-\*-', re.I).search

# 正则表达式,用于检测文件是否采用自由格式的 Fortran
_free_f90_start = re.compile(r'[^c*!]\s*[^\s\d\t]', re.I).match

def is_free_format(file):
    """Check if file is in free format Fortran."""
    # 默认假设为 fixed 格式,除非检测到自由格式的迹象。
    result = 0
    with open(file, encoding='latin1') as f:
        line = f.readline()
        n = 10000  # 扫描非注释行的最大行数
        if _has_f_header(line) or _has_fix_header(line):
            n = 0
        elif _has_f90_header(line):
            n = 0
            result = 1
        while n > 0 and line:
            line = line.rstrip()
            if line and line[0] != '!':
                n -= 1
                # 检测自由格式 Fortran 的特征
                if (line[0] != '\t' and _free_f90_start(line[:5])) or line[-1:] == '&':
                    result = 1
                    break
            line = f.readline()
    return result

def has_f90_header(src):
    """Check if source file has an f90 header."""
    with open(src, encoding='latin1') as f:
        line = f.readline()
    return _has_f90_header(line) or _has_fix_header(line)

# 正则表达式,用于解析 Fortran 77 代码中的编译器标志
_f77flags_re = re.compile(r'(c|)f77flags\s*\(\s*(?P<fcname>\w+)\s*\)\s*=\s*(?P<fflags>.*)', re.I)

def get_f77flags(src):
    """
    Search the first 20 lines of fortran 77 code for line pattern
      `CF77FLAGS(<fcompiler type>)=<f77 flags>`
    Return a dictionary {<fcompiler type>:<f77 flags>}.
    """
    flags = {}
    with open(src, encoding='latin1') as f:
        i = 0
        for line in f:
            i += 1
            if i > 20:
                break
            m = _f77flags_re.match(line)
            if not m:
                continue
            fcname = m.group('fcname').strip()
            fflags = m.group('fflags').strip()
            flags[fcname] = split_quoted(fflags)
    return flags

# TODO: implement get_f90flags and use it in _compile similarly to get_f77flags

if __name__ == '__main__':
    show_fcompilers()

.\numpy\numpy\distutils\from_template.py

"""
process_file(filename)

  takes templated file .xxx.src and produces .xxx file where .xxx
  is .pyf .f90 or .f using the following template rules:

  '<..>' denotes a template.

  All function and subroutine blocks in a source file with names that
  contain '<..>' will be replicated according to the rules in '<..>'.

  The number of comma-separated words in '<..>' will determine the number of
  replicates.

  '<..>' may have two different forms, named and short. For example,

  named:
   <p=d,s,z,c> where anywhere inside a block '<p>' will be replaced with
   'd', 's', 'z', and 'c' for each replicate of the block.

   <_c>  is already defined: <_c=s,d,c,z>
   <_t>  is already defined: <_t=real,double precision,complex,double complex>

  short:
   <s,d,c,z>, a short form of the named, useful when no <p> appears inside
   a block.

  In general, '<..>' contains a comma separated list of arbitrary
  expressions. If these expression must contain a comma|leftarrow|rightarrow,
  then prepend the comma|leftarrow|rightarrow with a backslash.

  If an expression matches '\\<index>' then it will be replaced
  by <index>-th expression.

  Note that all '<..>' forms in a block must have the same number of
  comma-separated entries.

 Predefined named template rules:
  <prefix=s,d,c,z>
  <ftype=real,double precision,complex,double complex>
  <ftypereal=real,double precision,\\0,\\1>
  <ctype=float,double,complex_float,complex_double>
  <ctypereal=float,double,\\0,\\1>

"""

__all__ = ['process_str', 'process_file']

import os
import sys
import re

# 正则表达式,用于识别函数和子程序的开始
routine_start_re = re.compile(r'(\n|\A)((     (\$|\*))|)\s*(subroutine|function)\b', re.I)
# 正则表达式,用于识别函数和子程序的结束
routine_end_re = re.compile(r'\n\s*end\s*(subroutine|function)\b.*(\n|\Z)', re.I)
# 正则表达式,用于识别函数的开始
function_start_re = re.compile(r'\n     (\$|\*)\s*function\b', re.I)

def parse_structure(astr):
    """ Return a list of tuples for each function or subroutine each
    tuple is the start and end of a subroutine or function to be
    expanded.
    """
    spanlist = []
    ind = 0
    # 循环直到找不到更多函数或子程序的起始位置
    while True:
        m = routine_start_re.search(astr, ind)
        if m is None:
            break
        start = m.start()
        if function_start_re.match(astr, start, m.end()):
            # 处理函数的情况,找到函数块的真正起始位置
            while True:
                i = astr.rfind('\n', ind, start)
                if i == -1:
                    break
                start = i
                if astr[i:i+7] != '\n     $':
                    break
        start += 1
        m = routine_end_re.search(astr, m.end())
        ind = end = m and m.end()-1 or len(astr)
        spanlist.append((start, end))
    return spanlist

# 正则表达式,用于识别模板
template_re = re.compile(r"<\s*(\w[\w\d]*)\s*>")
# 正则表达式,用于识别命名模板
named_re = re.compile(r"<\s*(\w[\w\d]*)\s*=\s*(.*?)\s*>")
# 正则表达式,用于识别简短模板
list_re = re.compile(r"<\s*((.*?))\s*>")

def find_repl_patterns(astr):
    reps = named_re.findall(astr)
    names = {}
    for rep in reps:
        # 遍历列表 `reps` 中的每个元素,每个元素是一个表示替换规则的列表 `rep`
        name = rep[0].strip() or unique_key(names)
        # 获取 `rep` 列表的第一个元素,并移除首尾空白,如果为空则调用 `unique_key(names)` 生成唯一键名
        repl = rep[1].replace(r'\,', '@comma@')
        # 获取 `rep` 列表的第二个元素,并替换其中的 `\,` 为 `@comma@`
        thelist = conv(repl)
        # 使用函数 `conv` 处理替换后的字符串 `repl`,并赋值给 `thelist`
        names[name] = thelist
        # 将 `name` 作为键,`thelist` 作为值添加到 `names` 字典中
    # 返回更新后的 `names` 字典
    return names
# 定义一个函数,用于查找并移除字符串中的替换模式,并返回处理后的字符串和模式名称列表
def find_and_remove_repl_patterns(astr):
    # 调用 find_repl_patterns 函数查找字符串中的替换模式,并将结果存储在 names 变量中
    names = find_repl_patterns(astr)
    # 使用正则表达式替换字符串中的命名替换模式,并获取替换后的字符串部分
    astr = re.subn(named_re, '', astr)[0]
    # 返回处理后的字符串和模式名称列表
    return astr, names

# 编译一个正则表达式,用于匹配形如 \数字 的字符串开头
item_re = re.compile(r"\A\\(?P<index>\d+)\Z")

# 定义一个函数,用于将输入字符串按逗号分割,并去除每个分割结果的首尾空格
def conv(astr):
    b = astr.split(',')
    l = [x.strip() for x in b]
    # 遍历列表中的每个元素,尝试用 item_re 正则表达式匹配开头是 \数字 的元素,并进行替换
    for i in range(len(l)):
        m = item_re.match(l[i])
        if m:
            j = int(m.group('index'))
            l[i] = l[j]
    # 将处理后的列表重新拼接成一个字符串,并用逗号连接
    return ','.join(l)

# 定义一个函数,用于生成一个唯一的键值,确保其不在给定字典中存在
def unique_key(adict):
    """ Obtain a unique key given a dictionary."""
    allkeys = list(adict.keys())
    done = False
    n = 1
    while not done:
        newkey = '__l%s' % (n)
        if newkey in allkeys:
            n += 1
        else:
            done = True
    return newkey

# 编译一个正则表达式,用于匹配模板名称的开头,形如 \w[\w\d]*
template_name_re = re.compile(r'\A\s*(\w[\w\d]*)\s*\Z')

# 定义一个函数,用于扩展子字符串,并处理特定的转义符号
def expand_sub(substr, names):
    # 将字符串中的特定转义符号替换为指定字符串
    substr = substr.replace(r'\>', '@rightarrow@')
    substr = substr.replace(r'\<', '@leftarrow@')
    # 调用 find_repl_patterns 函数查找字符串中的替换模式,并将结果存储在 lnames 变量中
    lnames = find_repl_patterns(substr)
    # 使用 named_re 正则表达式替换 substr 中的定义模板,将其替换为 <\1> 形式
    substr = named_re.sub(r"<\1>", substr)

    # 定义一个函数,用于处理列表的替换操作
    def listrepl(mobj):
        # 将匹配到的内容中的特定转义符号替换为 @comma@,然后调用 conv 函数处理
        thelist = conv(mobj.group(1).replace(r'\,', '@comma@'))
        # 如果列表模板名匹配 template_name_re,返回形如 <模板名> 的字符串
        if template_name_re.match(thelist):
            return "<%s>" % (thelist)
        name = None
        # 检查当前列表是否已经在字典中存在,如果不存在则生成唯一的键名
        for key in lnames.keys():
            if lnames[key] == thelist:
                name = key
        if name is None:
            name = unique_key(lnames)
            lnames[name] = thelist
        return "<%s>" % name

    # 使用 list_re 正则表达式将 substr 中的所有列表替换为命名模板
    substr = list_re.sub(listrepl, substr)

    # 初始化变量
    numsubs = None
    base_rule = None
    rules = {}

    # 遍历所有在模板正则表达式中匹配到的内容
    for r in template_re.findall(substr):
        if r not in rules:
            # 获取当前模板在 lnames 或 names 中对应的替换列表
            thelist = lnames.get(r, names.get(r, None))
            if thelist is None:
                raise ValueError('No replicates found for <%s>' % (r))
            if r not in names and not thelist.startswith('_'):
                names[r] = thelist
            # 将获取的替换列表转换为字符串数组,并获取其长度
            rule = [i.replace('@comma@', ',') for i in thelist.split(',')]
            num = len(rule)

            if numsubs is None:
                numsubs = num
                rules[r] = rule
                base_rule = r
            elif num == numsubs:
                rules[r] = rule
            else:
                print("Mismatch in number of replacements (base <%s=%s>)"
                      " for <%s=%s>. Ignoring." %
                      (base_rule, ','.join(rules[base_rule]), r, thelist))
    
    # 如果 rules 为空,直接返回 substr
    if not rules:
        return substr

    # 定义一个函数,用于替换 substr 中的模板名称为具体的替换内容
    def namerepl(mobj):
        name = mobj.group(1)
        return rules.get(name, (k+1)*[name])[k]

    # 初始化 newstr 变量为空字符串
    newstr = ''
    # 遍历 numsubs 次,依次替换 substr 中的模板名称为具体的替换内容,并添加换行符
    for k in range(numsubs):
        newstr += template_re.sub(namerepl, substr) + '\n\n'

    # 将字符串中的特定转义符号替换回原始符号
    newstr = newstr.replace('@rightarrow@', '>')
    newstr = newstr.replace('@leftarrow@', '<')
    # 返回处理后的字符串
    return newstr

# 定义一个函数,用于处理输入的字符串
def process_str(allstr):
    newstr = allstr
    # 初始化一个空字符串,用于存储最终生成的字符串
    writestr = ''

    # 调用 parse_structure 函数解析 newstr,返回结构化的数据结构
    struct = parse_structure(newstr)

    # 初始化旧结束位置为 0
    oldend = 0
    # 初始化一个空字典 names,并添加 _special_names 中的特殊名称
    names = {}
    names.update(_special_names)
    
    # 遍历结构化数据
    for sub in struct:
        # 在 newstr 的 oldend 到 sub[0] 之间查找和移除替换模式,并返回清理后的字符串和定义
        cleanedstr, defs = find_and_remove_repl_patterns(newstr[oldend:sub[0]])
        # 将清理后的字符串添加到 writestr 中
        writestr += cleanedstr
        # 更新 names 字典,添加新的定义
        names.update(defs)
        # 将 newstr 中 sub[0] 到 sub[1] 之间的子字符串进行展开,使用当前的 names 字典
        writestr += expand_sub(newstr[sub[0]:sub[1]], names)
        # 更新 oldend 为当前 sub 的结束位置 sub[1]
        oldend = sub[1]
    
    # 将剩余的 newstr 中 oldend 之后的部分添加到 writestr 中
    writestr += newstr[oldend:]

    # 返回生成的最终字符串 writestr
    return writestr
# 匹配包含语句的正则表达式,用于查找形如 `include 'filename.src'` 的字符串
include_src_re = re.compile(r"(\n|\A)\s*include\s*['\"](?P<name>[\w\d./\\]+\.src)['\"]", re.I)

# 解析包含语句并替换成实际内容后返回所有行的列表
def resolve_includes(source):
    # 获取源文件所在目录
    d = os.path.dirname(source)
    # 打开源文件并逐行处理
    with open(source) as fid:
        lines = []
        for line in fid:
            # 尝试匹配包含语句的正则表达式
            m = include_src_re.match(line)
            if m:
                # 获取包含文件的文件名
                fn = m.group('name')
                # 如果文件名不是绝对路径,则与源文件目录拼接
                if not os.path.isabs(fn):
                    fn = os.path.join(d, fn)
                # 如果包含文件存在,则递归处理其内容,否则直接添加原始行
                if os.path.isfile(fn):
                    lines.extend(resolve_includes(fn))
                else:
                    lines.append(line)
            else:
                # 如果不是包含语句,则直接添加原始行
                lines.append(line)
    return lines

# 处理文件,首先解析包含语句,然后处理成字符串返回
def process_file(source):
    lines = resolve_includes(source)
    return process_str(''.join(lines))

# 查找特殊名称的替换模式并返回
_special_names = find_repl_patterns('''
<_c=s,d,c,z>
<_t=real,double precision,complex,double complex>
<prefix=s,d,c,z>
<ftype=real,double precision,complex,double complex>
<ctype=float,double,complex_float,complex_double>
<ftypereal=real,double precision,\\0,\\1>
<ctypereal=float,double,\\0,\\1>
''')

# 主函数,处理命令行参数,读取和写入文件内容,并进行处理
def main():
    try:
        file = sys.argv[1]
    except IndexError:
        fid = sys.stdin
        outfile = sys.stdout
    else:
        fid = open(file, 'r')
        (base, ext) = os.path.splitext(file)
        newname = base
        outfile = open(newname, 'w')

    # 读取输入文件的全部内容
    allstr = fid.read()
    # 处理全部内容成字符串
    writestr = process_str(allstr)
    # 将处理后的内容写入输出文件
    outfile.write(writestr)

# 如果作为主程序运行,则调用主函数
if __name__ == "__main__":
    main()

.\numpy\numpy\distutils\fujitsuccompiler.py

# 导入 UnixCCompiler 类,用于扩展 FujitsuCCompiler 类
from distutils.unixccompiler import UnixCCompiler

class FujitsuCCompiler(UnixCCompiler):
    """
    Fujitsu compiler.
    继承自 UnixCCompiler 类,用于支持 Fujitsu 编译器的编译功能。
    """

    # 设置编译器类型为 'fujitsu'
    compiler_type = 'fujitsu'
    
    # 指定 C 编译器执行文件名为 'fcc'
    cc_exe = 'fcc'
    
    # 指定 C++ 编译器执行文件名为 'FCC'
    cxx_exe = 'FCC'

    # 初始化方法,接收 verbose、dry_run 和 force 三个参数
    def __init__(self, verbose=0, dry_run=0, force=0):
        # 调用父类 UnixCCompiler 的初始化方法
        UnixCCompiler.__init__(self, verbose, dry_run, force)
        
        # 将 C 编译器执行文件名 'fcc' 赋值给 cc_compiler 变量
        cc_compiler = self.cc_exe
        
        # 将 C++ 编译器执行文件名 'FCC' 赋值给 cxx_compiler 变量
        
        # 设置编译器的可执行文件路径及编译选项
        self.set_executables(
            # 设置 C 编译器的可执行文件路径及编译选项,包括优化级别为 3、禁用 clang 扩展、生成位置独立代码
            compiler=cc_compiler +
            ' -O3 -Nclang -fPIC',
            # 设置用于编译单个 C 源文件的编译器选项与路径,与上一行设置相同
            compiler_so=cc_compiler +
            ' -O3 -Nclang -fPIC',
            # 设置 C++ 编译器的可执行文件路径及编译选项,与上一行设置相同
            compiler_cxx=cxx_compiler +
            ' -O3 -Nclang -fPIC',
            # 设置链接器的可执行文件路径及链接选项,链接库包括 f90、f90f、fjsrcinfo、elf,并生成共享库
            linker_exe=cc_compiler +
            ' -lfj90i -lfj90f -lfjsrcinfo -lelf -shared',
            # 设置用于链接共享库的链接器选项与路径,与上一行设置相同
            linker_so=cc_compiler +
            ' -lfj90i -lfj90f -lfjsrcinfo -lelf -shared'
        )

.\numpy\numpy\distutils\intelccompiler.py

# 导入平台模块,用于获取操作系统信息
import platform

# 导入 UnixCCompiler 类,用于处理 Unix-like 系统上的编译器相关操作
from distutils.unixccompiler import UnixCCompiler

# 导入 find_executable 函数,用于查找可执行文件路径
from numpy.distutils.exec_command import find_executable

# 导入 simple_version_match 函数,用于简单的版本匹配功能
from numpy.distutils.ccompiler import simple_version_match

# 如果当前操作系统是 Windows,导入 MSVCCompiler 类
if platform.system() == 'Windows':
    from numpy.distutils.msvc9compiler import MSVCCompiler


class IntelCCompiler(UnixCCompiler):
    """A modified Intel compiler compatible with a GCC-built Python."""
    # 编译器类型为 Intel
    compiler_type = 'intel'
    # 编译器可执行文件为 icc
    cc_exe = 'icc'
    # 编译参数为 fPIC
    cc_args = 'fPIC'

    def __init__(self, verbose=0, dry_run=0, force=0):
        # 调用父类 UnixCCompiler 的初始化方法
        UnixCCompiler.__init__(self, verbose, dry_run, force)

        # 获取编译器版本信息
        v = self.get_version()
        # 根据版本选择 OpenMP 选项
        mpopt = 'openmp' if v and v < '15' else 'qopenmp'
        # 设置编译器可执行文件及参数
        self.cc_exe = ('icc -fPIC -fp-model strict -O3 '
                       '-fomit-frame-pointer -{}').format(mpopt)
        compiler = self.cc_exe

        # 根据操作系统选择共享库标志
        if platform.system() == 'Darwin':
            shared_flag = '-Wl,-undefined,dynamic_lookup'
        else:
            shared_flag = '-shared'
        # 设置编译器及链接器的可执行文件
        self.set_executables(compiler=compiler,
                             compiler_so=compiler,
                             compiler_cxx=compiler,
                             archiver='xiar' + ' cru',
                             linker_exe=compiler + ' -shared-intel',
                             linker_so=compiler + ' ' + shared_flag +
                             ' -shared-intel')


class IntelItaniumCCompiler(IntelCCompiler):
    # 编译器类型为 Itanium Intel
    compiler_type = 'intele'

    # 在 Itanium 平台上,Intel 编译器曾被称为 ecc,现在也可以是 icc,因此搜索这两个可执行文件
    for cc_exe in map(find_executable, ['icc', 'ecc']):
        if cc_exe:
            break


class IntelEM64TCCompiler(UnixCCompiler):
    """
    A modified Intel x86_64 compiler compatible with a 64bit GCC-built Python.
    """
    # 编译器类型为 Intel x86_64
    compiler_type = 'intelem'
    # 编译器可执行文件为 icc -m64
    cc_exe = 'icc -m64'
    # 编译参数为 -fPIC
    cc_args = '-fPIC'

    def __init__(self, verbose=0, dry_run=0, force=0):
        # 调用父类 UnixCCompiler 的初始化方法
        UnixCCompiler.__init__(self, verbose, dry_run, force)

        # 获取编译器版本信息
        v = self.get_version()
        # 根据版本选择 OpenMP 选项
        mpopt = 'openmp' if v and v < '15' else 'qopenmp'
        # 设置编译器可执行文件及参数
        self.cc_exe = ('icc -std=c99 -m64 -fPIC -fp-model strict -O3 '
                       '-fomit-frame-pointer -{}').format(mpopt)
        compiler = self.cc_exe

        # 根据操作系统选择共享库标志
        if platform.system() == 'Darwin':
            shared_flag = '-Wl,-undefined,dynamic_lookup'
        else:
            shared_flag = '-shared'
        # 设置编译器及链接器的可执行文件
        self.set_executables(compiler=compiler,
                             compiler_so=compiler,
                             compiler_cxx=compiler,
                             archiver='xiar' + ' cru',
                             linker_exe=compiler + ' -shared-intel',
                             linker_so=compiler + ' ' + shared_flag +
                             ' -shared-intel')

# 如果当前操作系统是 Windows,则执行以下代码段
if platform.system() == 'Windows':
    # 继承自 MSVCCompiler 类,代表一个修改过的 Intel C 编译器,与 MSVC 构建的 Python 兼容
    class IntelCCompilerW(MSVCCompiler):
        """
        A modified Intel compiler compatible with an MSVC-built Python.
        """
        
        # 编译器类型标识为 'intelw'
        compiler_type = 'intelw'
        
        # C++ 编译器为 'icl'
        compiler_cxx = 'icl'

        # 构造函数,初始化对象
        def __init__(self, verbose=0, dry_run=0, force=0):
            # 调用父类 MSVCCompiler 的构造函数进行初始化
            MSVCCompiler.__init__(self, verbose, dry_run, force)
            
            # 使用正则表达式匹配 Intel 编译器的版本信息
            version_match = simple_version_match(start=r'Intel\(R\).*?32,')
            self.__version = version_match

        # 初始化函数,用于设置编译器相关的路径和选项
        def initialize(self, plat_name=None):
            # 调用父类 MSVCCompiler 的初始化函数
            MSVCCompiler.initialize(self, plat_name)
            
            # 设置 C 编译器的可执行文件路径
            self.cc = self.find_exe('icl.exe')
            
            # 设置库文件的可执行文件路径
            self.lib = self.find_exe('xilib')
            
            # 设置链接器的可执行文件路径
            self.linker = self.find_exe('xilink')
            
            # 设置编译选项,优化等级为 O3,使用标准为 c99,禁用 logo 和警告
            self.compile_options = ['/nologo', '/O3', '/MD', '/W3',
                                    '/Qstd=c99']
            
            # 调试模式下的编译选项,包括禁用 logo、启用调试符号、启用调试宏等
            self.compile_options_debug = ['/nologo', '/Od', '/MDd', '/W3',
                                          '/Qstd=c99', '/Z7', '/D_DEBUG']

    # 继承自 IntelCCompilerW 类,代表一个修改过的 Intel x86_64 编译器,与 64 位 MSVC 构建的 Python 兼容
    class IntelEM64TCCompilerW(IntelCCompilerW):
        """
        A modified Intel x86_64 compiler compatible with
        a 64bit MSVC-built Python.
        """
        
        # 编译器类型标识为 'intelemw'
        compiler_type = 'intelemw'

        # 构造函数,初始化对象
        def __init__(self, verbose=0, dry_run=0, force=0):
            # 调用父类 IntelCCompilerW 的构造函数进行初始化
            MSVCCompiler.__init__(self, verbose, dry_run, force)
            
            # 使用正则表达式匹配 Intel 编译器的版本信息
            version_match = simple_version_match(start=r'Intel\(R\).*?64,')
            self.__version = version_match

.\numpy\numpy\distutils\lib2def.py

import re
import sys
import subprocess
# 导入re模块、sys模块和subprocess模块

__doc__ = """This module generates a DEF file from the symbols in
an MSVC-compiled DLL import library.  It correctly discriminates between
data and functions.  The data is collected from the output of the program
nm(1).

Usage:
    python lib2def.py [libname.lib] [output.def]
or
    python lib2def.py [libname.lib] > output.def

libname.lib defaults to python<py_ver>.lib and output.def defaults to stdout

Author: Robert Kern <kernr@mail.ncifcrf.gov>
Last Update: April 30, 1999
"""
# 模块文档字符串说明

__version__ = '0.1a'
# 模块的版本号

py_ver = "%d%d" % tuple(sys.version_info[:2])
# 获取当前Python版本信息拼接成字符串

DEFAULT_NM = ['nm', '-Cs']
# 默认的nm命令参数列表

DEF_HEADER = """LIBRARY         python%s.dll
;CODE           PRELOAD MOVEABLE DISCARDABLE
;DATA           PRELOAD SINGLE

EXPORTS
""" % py_ver
# DEF文件的头部信息

FUNC_RE = re.compile(r"^(.*) in python%s\.dll" % py_ver, re.MULTILINE)
# 匹配函数的正则表达式

DATA_RE = re.compile(r"^_imp__(.*) in python%s\.dll" % py_ver, re.MULTILINE)
# 匹配数据的正则表达式

def parse_cmd():
    """Parses the command-line arguments.

libfile, deffile = parse_cmd()"""
    # 解析命令行参数的函数说明

    if len(sys.argv) == 3:
        if sys.argv[1][-4:] == '.lib' and sys.argv[2][-4:] == '.def':
            libfile, deffile = sys.argv[1:]
        elif sys.argv[1][-4:] == '.def' and sys.argv[2][-4:] == '.lib':
            deffile, libfile = sys.argv[1:]
        else:
            print("I'm assuming that your first argument is the library")
            print("and the second is the DEF file.")
    elif len(sys.argv) == 2:
        if sys.argv[1][-4:] == '.def':
            deffile = sys.argv[1]
            libfile = 'python%s.lib' % py_ver
        elif sys.argv[1][-4:] == '.lib':
            deffile = None
            libfile = sys.argv[1]
    else:
        libfile = 'python%s.lib' % py_ver
        deffile = None
    return libfile, deffile
# 解析命令行参数的函数

def getnm(nm_cmd=['nm', '-Cs', 'python%s.lib' % py_ver], shell=True):
    """Returns the output of nm_cmd via a pipe.

nm_output = getnm(nm_cmd = 'nm -Cs py_lib')"""
    # 获取nm命令输出的函数说明

    p = subprocess.Popen(nm_cmd, shell=shell, stdout=subprocess.PIPE,
                         stderr=subprocess.PIPE, text=True)
    nm_output, nm_err = p.communicate()
    if p.returncode != 0:
        raise RuntimeError('failed to run "%s": "%s"' % (
                                     ' '.join(nm_cmd), nm_err))
    return nm_output
# 执行nm命令获取输出的函数

def parse_nm(nm_output):
    """Returns a tuple of lists: dlist for the list of data
symbols and flist for the list of function symbols.

dlist, flist = parse_nm(nm_output)"""
    # 解析nm命令输出内容的函数说明

    data = DATA_RE.findall(nm_output)
    func = FUNC_RE.findall(nm_output)

    flist = []
    for sym in data:
        if sym in func and (sym[:2] == 'Py' or sym[:3] == '_Py' or sym[:4] == 'init'):
            flist.append(sym)

    dlist = []
    for sym in data:
        if sym not in flist and (sym[:2] == 'Py' or sym[:3] == '_Py'):
            dlist.append(sym)

    dlist.sort()
    flist.sort()
    return dlist, flist
# 解析nm命令输出内容的函数

def output_def(dlist, flist, header, file = sys.stdout):
    """将最终的 DEF 文件输出到默认的 stdout 或指定的文件中。
def output_def(dlist, flist, header, file=sys.stdout):
    # 遍历数据符号列表,为每个符号生成一个数据行并添加到标题中
    for data_sym in dlist:
        header = header + '\t%s DATA\n' % data_sym
    # 添加一个空行到标题末尾
    header = header + '\n'
    # 遍历函数符号列表,为每个符号生成一个函数行并添加到标题中
    for func_sym in flist:
        header = header + '\t%s\n' % func_sym
    # 将最终的标题写入指定的文件对象中
    file.write(header)

if __name__ == '__main__':
    # 解析命令行参数,获取库文件和定义文件路径
    libfile, deffile = parse_cmd()
    # 如果定义文件路径为None,则将输出定向到标准输出流
    if deffile is None:
        deffile = sys.stdout
    else:
        # 否则,打开指定路径的文件以便写入
        deffile = open(deffile, 'w')
    # 构造执行 nm 命令的参数列表,并获取 nm 输出结果
    nm_cmd = DEFAULT_NM + [str(libfile)]
    nm_output = getnm(nm_cmd, shell=False)
    # 解析 nm 输出,获取数据符号列表和函数符号列表
    dlist, flist = parse_nm(nm_output)
    # 调用 output_def 函数,生成输出文件的定义部分
    output_def(dlist, flist, DEF_HEADER, deffile)

.\numpy\numpy\distutils\line_endings.py

# 导入必要的库:os(操作系统相关)、re(正则表达式)、sys(系统相关)
import os
import re
import sys

# 定义函数 dos2unix,用于将 DOS 格式的换行符转换为 UNIX 格式的换行符
def dos2unix(file):
    # 如果传入的文件名是一个目录,则打印出信息并返回
    if os.path.isdir(file):
        print(file, "Directory!")
        return

    # 使用二进制模式打开文件
    with open(file, "rb") as fp:
        data = fp.read()
    
    # 检查文件中是否包含 NULL 字符,若包含则打印出信息并返回
    if b'\0' in data:
        print(file, "Binary!")
        return

    # 将文件中的 CRLF(\r\n)替换为 LF(\n)
    newdata = re.sub(b"\r\n", b"\n", data)

    # 如果替换后的数据与原始数据不同,则表示文件内容已修改
    if newdata != data:
        print('dos2unix:', file)
        # 将修改后的数据写回文件
        with open(file, "wb") as f:
            f.write(newdata)
        return file
    else:
        # 如果文件内容未变化,则打印出文件名和 'ok'
        print(file, 'ok')

# 定义函数 dos2unix_one_dir,用于处理指定目录下的所有文件,并将修改过的文件名加入到 modified_files 列表中
def dos2unix_one_dir(modified_files, dir_name, file_names):
    for file in file_names:
        full_path = os.path.join(dir_name, file)
        # 调用 dos2unix 函数处理每个文件,并将返回值(修改过的文件名)加入到 modified_files 列表中
        file = dos2unix(full_path)
        if file is not None:
            modified_files.append(file)

# 定义函数 dos2unix_dir,用于处理指定目录下的所有文件夹及其文件,并返回所有修改过的文件名列表
def dos2unix_dir(dir_name):
    modified_files = []
    # 递归遍历指定目录,对每个文件夹调用 dos2unix_one_dir 函数处理
    os.path.walk(dir_name, dos2unix_one_dir, modified_files)
    return modified_files
#----------------------------------

# 定义函数 unix2dos,用于将 UNIX 格式的换行符转换为 DOS 格式的换行符
def unix2dos(file):
    # 如果传入的文件名是一个目录,则打印出信息并返回
    if os.path.isdir(file):
        print(file, "Directory!")
        return

    # 使用二进制模式打开文件
    with open(file, "rb") as fp:
        data = fp.read()

    # 检查文件中是否包含 NULL 字符,若包含则打印出信息并返回
    if b'\0' in data:
        print(file, "Binary!")
        return

    # 将文件中的 CRLF(\r\n)替换为 LF(\n)
    newdata = re.sub(b"\r\n", b"\n", data)
    # 将 LF(\n)替换为 CRLF(\r\n)
    newdata = re.sub(b"\n", b"\r\n", newdata)

    # 如果替换后的数据与原始数据不同,则表示文件内容已修改
    if newdata != data:
        print('unix2dos:', file)
        # 将修改后的数据写回文件
        with open(file, "wb") as f:
            f.write(newdata)
        return file
    else:
        # 如果文件内容未变化,则打印出文件名和 'ok'
        print(file, 'ok')

# 定义函数 unix2dos_one_dir,用于处理指定目录下的所有文件,并将修改过的文件名加入到 modified_files 列表中
def unix2dos_one_dir(modified_files, dir_name, file_names):
    for file in file_names:
        full_path = os.path.join(dir_name, file)
        # 调用 unix2dos 函数处理每个文件,并将返回值(修改过的文件名)加入到 modified_files 列表中
        unix2dos(full_path)
        if file is not None:
            modified_files.append(file)

# 定义函数 unix2dos_dir,用于处理指定目录下的所有文件夹及其文件,并返回所有修改过的文件名列表
def unix2dos_dir(dir_name):
    modified_files = []
    # 递归遍历指定目录,对每个文件夹调用 unix2dos_one_dir 函数处理
    os.path.walk(dir_name, unix2dos_one_dir, modified_files)
    return modified_files

# 当脚本被直接执行时,调用 dos2unix_dir 函数,处理传入的第一个参数作为目录名
if __name__ == "__main__":
    dos2unix_dir(sys.argv[1])

.\numpy\numpy\distutils\log.py

# Colored log
# 引入 sys 模块,用于访问系统标准输入输出等功能
import sys
# 从 distutils.log 中导入所有内容,不触发 F403 警告
from distutils.log import *  # noqa: F403
# 从 distutils.log 中导入 Log 类别名为 old_Log
from distutils.log import Log as old_Log
# 从 distutils.log 中导入全局日志对象 _global_log
from distutils.log import _global_log

# 从 numpy.distutils.misc_util 中导入一些函数和变量
from numpy.distutils.misc_util import (red_text, default_text, cyan_text,
        green_text, is_sequence, is_string)


# 定义修复参数的函数 _fix_args,参数 args 可能是字符串或序列,默认 flag 为 1
def _fix_args(args, flag=1):
    # 如果 args 是字符串,则替换 '%' 为 '%%'
    if is_string(args):
        return args.replace('%', '%%')
    # 如果 flag 为真且 args 是序列,则递归修复序列中的每个元素
    if flag and is_sequence(args):
        return tuple([_fix_args(a, flag=0) for a in args])
    return args  # 返回修复后的参数


# 定义 Log 类,继承自 old_Log
class Log(old_Log):
    # 内部方法,用于记录日志消息
    def _log(self, level, msg, args):
        # 如果日志级别大于等于设定的阈值
        if level >= self.threshold:
            # 如果 args 不为空,则格式化消息中的参数
            if args:
                msg = msg % _fix_args(args)
            # 如果 0:(始终为假条件,所以不会执行以下代码块)
            if 0:
                if msg.startswith('copying ') and msg.find(' -> ') != -1:
                    return
                if msg.startswith('byte-compiling '):
                    return
            # 打印经过全局颜色映射处理后的消息
            print(_global_color_map[level](msg))
            sys.stdout.flush()  # 刷新标准输出流

    # 自定义的记录“好”消息的方法
    def good(self, msg, *args):
        """
        If we log WARN messages, log this message as a 'nice' anti-warn
        message.

        """
        # 如果 WARN 级别大于等于设定的阈值
        if WARN >= self.threshold:
            # 如果 args 不为空,则打印经过绿色文本处理后的消息
            if args:
                print(green_text(msg % _fix_args(args)))
            else:
                print(green_text(msg))
            sys.stdout.flush()  # 刷新标准输出流


# 将 _global_log 对象的类设置为 Log,覆盖原有全局日志对象
_global_log.__class__ = Log

# 定义 good 函数,直接调用 _global_log 的 good 方法
good = _global_log.good

# 定义设置日志级别阈值的函数 set_threshold
def set_threshold(level, force=False):
    # 获取当前的日志级别阈值
    prev_level = _global_log.threshold
    # 如果当前级别高于 DEBUG 或者 force 参数为真
    if prev_level > DEBUG or force:
        # 如果当前级别小于等于 DEBUG,则将全局日志对象的阈值设置为指定级别
        _global_log.threshold = level
        if level <= DEBUG:
            # 如果设置级别为 DEBUG,则记录信息到控制台
            info('set_threshold: setting threshold to DEBUG level,'
                    ' it can be changed only with force argument')
    else:
        # 如果当前级别不高于 DEBUG 且 force 参数为假,则记录信息到控制台
        info('set_threshold: not changing threshold from DEBUG level'
                ' %s to %s' % (prev_level, level))
    return prev_level  # 返回之前的日志级别阈值

# 定义获取当前日志级别阈值的函数 get_threshold
def get_threshold():
    return _global_log.threshold  # 返回全局日志对象的当前阈值

# 定义设置日志详细程度的函数 set_verbosity
def set_verbosity(v, force=False):
    # 获取当前的日志级别阈值
    prev_level = _global_log.threshold
    # 如果 v 小于 0,则设置阈值为 ERROR 级别
    if v < 0:
        set_threshold(ERROR, force)
    # 如果 v 等于 0,则设置阈值为 WARN 级别
    elif v == 0:
        set_threshold(WARN, force)
    # 如果 v 等于 1,则设置阈值为 INFO 级别
    elif v == 1:
        set_threshold(INFO, force)
    # 如果 v 大于等于 2,则设置阈值为 DEBUG 级别
    elif v >= 2:
        set_threshold(DEBUG, force)
    # 返回之前的日志级别对应的数值
    return {FATAL:-2,ERROR:-1,WARN:0,INFO:1,DEBUG:2}.get(prev_level, 1)

# 定义全局颜色映射字典,将不同级别的日志映射到对应的颜色处理函数
_global_color_map = {
    DEBUG: cyan_text,
    INFO: default_text,
    WARN: red_text,
    ERROR: red_text,
    FATAL: red_text
}

# 设置日志详细程度为 WARN 级别,强制更新
set_verbosity(0, force=True)

# 将 _error、_warn、_info、_debug 四个函数赋值给新的变量名
_error = error
_warn = warn
_info = info
_debug = debug

# 定义 error 函数,对原 _error 函数进行封装,添加前缀 "ERROR: "
def error(msg, *a, **kw):
    _error(f"ERROR: {msg}", *a, **kw)

# 定义 warn 函数,对原 _warn 函数进行封装,添加前缀 "WARN: "
def warn(msg, *a, **kw):
    _warn(f"WARN: {msg}", *a, **kw)

# 定义 info 函数,对原 _info 函数进行封装,添加前缀 "INFO: "
def info(msg, *a, **kw):
    _info(f"INFO: {msg}", *a, **kw)

# 定义 debug 函数,对原 _debug 函数进行封装,添加前缀 "DEBUG: "
def debug(msg, *a, **kw):
    _debug(f"DEBUG: {msg}", *a, **kw)

.\numpy\numpy\distutils\mingw\gfortran_vs2003_hack.c

# 定义一个名为 _get_output_format 的函数,返回整数值 0
int _get_output_format(void)
{
    return 0;
}

# 定义一个名为 _imp____lc_codepage 的整型变量,并将其初始化为 0
int _imp____lc_codepage = 0;

.\numpy\numpy\distutils\mingw32ccompiler.py

"""
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 g77

import 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)

def get_msvcr_replacement():
    """Replacement for outdated version of get_msvcr from cygwinccompiler"""
    msvcr = msvc_runtime_library()
    return [] if msvcr is None else [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 parameters
class Mingw32CCompiler(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=[]
        return
    def link(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:
            # 如果没有传入库列表,则初始化为空列表
            if not 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])
        # 函数无返回值,直接结束
        return



    def object_filenames (self,
                          source_filenames,
                          strip_dir=0,
                          output_dir=''):
        # 如果未指定输出目录,则将其设为空字符串
        if output_dir is None: 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 not in (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
def find_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 well
    if '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 DLL
    for 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 error
    raise ValueError("%s not found in %s" % (dllname, lib_dirs))


def dump_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')


def generate_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 table
    for i in range(len(dump)):
        if _START.match(dump[i].decode()):
            break
    else:
        raise ValueError("Symbol table not found")

    # extract symbols from the symbol table
    syms = []
    for j in range(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 warning
    if len(syms) == 0:
        log.warn('No symbols found in %s' % dll)

    # write the symbols into the .def file
    with open(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])


def find_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 目录不存在,则返回 None
        if not os.path.exists(winsxs_path):
            return None
        # 遍历 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 文件,则返回 None
        return None

    # 在 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)库文件
def build_msvcr_library(debug=False):
    # 如果操作系统不是 Windows,则返回 False
    if os.name != 'nt':
        return False
    
    # 获取 MSVC runtime 的版本号
    msvcr_ver = msvc_runtime_major()
    # 如果版本号为 None,说明未找到 MSVC runtime,返回 False
    if msvcr_ver is None:
        log.debug('Skip building import library: Runtime is not compiled with MSVC')
        return False
    
    # 跳过版本小于 MSVC 8.0 的自定义库
    if msvcr_ver < 80:
        log.debug('Skip building msvcr library: custom functionality not present')
        return False
    
    # 获取 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,))
        return True
    
    # 查找 msvcr.dll 文件
    msvcr_dll_name = msvcr_name + '.dll'
    dll_file = find_dll(msvcr_dll_name)
    # 如果未找到 msvcr.dll 文件,返回 False
    if not dll_file:
        log.warn('Cannot build msvcr library: "%s" not found' % msvcr_dll_name)
        return False
    
    # 生成 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 运行时的导入库
def build_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]
    if hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix:
        stems.append(sys.base_prefix)
    elif hasattr(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)
    if not os.path.isfile(lib_file):
        # 如果在虚拟环境中找不到库文件,尝试基本分发目录,并在那里找到使用
        # 对于 Python 2.7 的虚拟环境,基本目录是 real_prefix 而不是 base_prefix
        if hasattr(sys, 'base_prefix'):
            base_lib = os.path.join(sys.base_prefix, 'libs', lib_name)
        elif hasattr(sys, 'real_prefix'):
            base_lib = os.path.join(sys.real_prefix, 'libs', lib_name)
        else:
            base_lib = ''  # os.path.isfile('') == False

        if 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)
    with open(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 is not None:  # 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, min
        del 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')

def msvc_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)) from None
    # 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 文件
def manifest_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 文件
    if type == 'dll':
        rctype = 2
    elif type == 'exe':
        rctype = 1
    else:
        raise ValueError("Type %s not supported" % type)

    return """\
#include "winuser.h"
%d RT_MANIFEST %s""" % (rctype, name)

# 检查嵌入的 msvcr 是否与链接的 msvcr 版本匹配
def check_embedded_msvcr_match_linked(msver):
    """msver is the ms runtime version used for the MANIFEST."""
    # 检查链接和嵌入的 msvcr 主版本号是否相同
    maj = msvc_runtime_major()
    if maj:
        if not maj == int(msver):
            raise ValueError(
                  "Discrepancy between linked msvcr " \
                  "(%d) and the one about to be embedded " \
                  "(%d)" % (int(msver), maj))

# 获取配置测试的名称(包括后缀)
def configtest_name(config):
    base = os.path.basename(config._gen_temp_sourcefile("yo", [], "c"))
    return os.path.splitext(base)[0]

# 获取清单文件的名称
def manifest_name(config):
    # 获取配置测试的名称(包括后缀)
    root = configtest_name(config)
    exext = config.compiler.exe_extension
    return root + exext + ".manifest"

# 获取 rc 文件的名称
def rc_name(config):
    # 获取配置测试的名称(包括后缀)
    root = configtest_name(config)
    return root + ".rc"

# 生成清单文件
def generate_manifest(config):
    msver = get_build_msvc_version()
    if msver is not None:
        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))
            with open(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 import reduce         # 提供高阶函数操作

import distutils    # Python 的标准库中的工具模块
from distutils.errors import DistutilsError  # 引入 distutils 中的错误异常类

# 线程本地存储,用于存储每个线程的临时目录,以确保每个线程只创建一个临时目录
_tdata = tlocal()

# 存储所有创建的临时目录,以便在退出时删除
_tmpdirs = []

def clean_up_temporary_directory():
    """
    清理临时目录的函数,在程序退出时被注册调用

    """
    if _tmpdirs is not None:
        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']

class InstallableLib:
    """
    可安装库的容器类,用于存储安装库的信息

    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   # 设置目标目录属性

def get_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,说明在配置阶段可能未定义,直接返回envjobs
    if dist is None:
        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,则返回envjobs
    if all(x is None for x in cmdattr):
        return envjobs
    else:
        # 返回三个命令对象中并行属性不为None的最大值
        return max(x for x in cmdattr if x is not None)
# 引用警告模块
import warnings

# 定义函数:将参数列表中的每个参数进行引号处理
def quote_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 in range(len(args)):
        a = args[i]
        if ' ' in a and a[0] not in '"\'':
            args[i] = '"%s"' % (a)
    
    # 返回处理后的参数列表
    return args

# 定义函数:将'/-'分隔的路径名转换为操作系统的路径分隔符
def allpath(name):
    "Convert a /-separated pathname to one using the OS's path separator."
    split = name.split('/')
    return os.path.join(*split)

# 定义函数:返回相对于父路径的路径
def rel_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))
    
    if len(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

# 定义函数:根据调用堆栈中的帧对象返回模块的路径
def get_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]
        
        if hasattr(mod, '__file__'):
            d = os.path.dirname(os.path.abspath(mod.__file__))
        else:
            # 执行setup.py时,返回当前目录的绝对路径
            d = os.path.abspath('.')
    
    # 如果指定了父路径,则返回相对于父路径的路径
    if parent_path is not None:
        d = rel_path(d, parent_path)
    
    # 返回模块路径或者当前目录(如果未找到模块路径)
    return d or '.'

# 定义函数:连接两个或多个路径名组件,解析'..'和'.',并使用操作系统的路径分隔符
def njoin(*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
    
    # 如果路径名组件为空,则返回空字符串
    if not 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行的内容
def get_mathlibs(path=None):
    """Return the MATHLIB line from numpyconfig.h
    """
    # 如果提供了路径,则使用给定路径下的_numpyconfig.h文件
    if path is not None:
        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
                break
        else:
            # 如果在所有目录中都找不到文件,则引发异常
            raise DistutilsError('_numpyconfig.h not found in numpy include '
                'dirs %r' % (dirs,))
    
    # 打开配置文件并读取内容
    with open(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

# 解析路径中的`..`和`.`,返回规范化后的路径
def minrelpath(path):
    """Resolve `..` and '.' from path.
    """
    # 如果路径不是字符串,则直接返回
    if not is_string(path):
        return path
    # 如果路径中没有`.`,则直接返回
    if '.' not in path:
        return path
    l = path.split(os.sep)
    while l:
        try:
            i = l.index('.', 1)
        except ValueError:
            break
        del l[i]
    j = 1
    while l:
        try:
            i = l.index('..', j)
        except ValueError:
            break
        if l[i-1]=='..':
            j += 1
        else:
            del l[i], l[i-1]
            j = 1
    # 如果路径列表为空,则返回空字符串;否则返回重新连接后的路径
    if not l:
        return ''
    return os.sep.join(l)

# 对glob.glob返回的结果进行排序,以解决https://bugs.python.org/issue30461问题
def sorted_glob(fileglob):
    """sorts output of python glob for https://bugs.python.org/issue30461
    to allow extensions to have reproducible build results"""
    # 对glob.glob的结果进行排序并返回
    return sorted(glob.glob(fileglob))

# 对路径列表进行修正,确保它是一个序列,并且不是字符串
def _fix_paths(paths, local_path, include_non_existing):
    assert is_sequence(paths), repr(type(paths))
    new_paths = []
    # 断言路径不是字符串,避免意外的类型错误
    assert not is_string(paths), repr(paths)
    # 遍历给定的路径列表 paths
    for n in paths:
        # 检查当前路径 n 是否为字符串
        if is_string(n):
            # 如果路径中包含通配符 '*' 或 '?',则使用 sorted_glob 函数获取匹配的路径列表 p
            if '*' 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 依然不存在,则打印不存在路径的信息
                    if not 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 函数,并根据需要添加本地路径
def gpaths(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)

# 创建临时文件,返回文件对象和文件名
def make_temp_file(suffix='', prefix='', text=True):
    # 如果 _tdata 没有 tempdir 属性,则创建临时目录
    if not hasattr(_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

# 用于彩色终端输出的钩子
def terminal_has_colors():
    # 如果是 cygwin 平台且未设置 USE_COLOR 环境变量,则返回 0
    if sys.platform=='cygwin' and 'USE_COLOR' not in os.environ:
        return 0
    # 如果标准输出是终端并且支持颜色
    if hasattr(sys.stdout, 'isatty') and sys.stdout.isatty():
        try:
            # 尝试导入 curses 模块
            import curses
            curses.setupterm()
            # 如果终端支持颜色功能,则返回 1
            if (curses.tigetnum("colors") >= 0
                and curses.tigetnum("pairs") >= 0
                and ((curses.tigetstr("setf") is not None
                      and curses.tigetstr("setb") is not None)
                     or (curses.tigetstr("setaf") is not None
                         and curses.tigetstr("setab") is not None)
                     or curses.tigetstr("scp") is not None)):
                return 1
        except Exception:
            pass
    # 其他情况返回 0
    return 0

# 如果终端支持颜色,则定义颜色代码和文本修饰函数
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)
    def colour_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:
    # 如果终端不支持颜色,则定义文本颜色修改函数
    def colour_text(s, fg=None, bg=None):
        return s

# 定义默认文本颜色修改函数
def default_text(s):
    return colour_text(s, 'default')
# 定义红色文本颜色修改函数
def red_text(s):
    return colour_text(s, 'red')
# 定义绿色文本颜色修改函数
def green_text(s):
    return colour_text(s, 'green')
# 定义黄色文本颜色修改函数
def yellow_text(s):
    return colour_text(s, 'yellow')
# 定义青色文本颜色修改函数
def cyan_text(s):
    return colour_text(s, 'cyan')
# 定义蓝色文本颜色修改函数
def blue_text(s):
    return colour_text(s, 'blue')

# 将 cygwin 路径转换为 win32 路径
def cyg2win32(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.html
    
    if sys.platform != "cygwin":
        # 如果不是在 Cygwin 平台上,直接返回原始路径
        return path
    # 调用子进程执行 cygpath 命令,传入参数"--windows"和路径,获取输出
    return subprocess.check_output(
        ["/usr/bin/cygpath", "--windows", path], text=True
    )
# 判断是否在mingw32环境中
def mingw32():
    """Return true when using mingw32 environment.
    """
    # 如果操作系统是win32
    if sys.platform=='win32':
        # 如果环境变量OSTYPE的值是'msys'
        if os.environ.get('OSTYPE', '')=='msys':
            return True
        # 如果环境变量MSYSTEM的值是'MINGW32'
        if os.environ.get('MSYSTEM', '')=='MINGW32':
            return True
    # 如果以上条件都不满足,则返回False
    return False

# 返回MSVC运行库的版本,由__MSC_VER__宏定义
def msvc_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 = None
    return msc_ver

# 返回Python是否使用MSVC构建的MSVC运行库的名称
def msvc_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:
        # 如果没有版本号,返回None
        return None

# 返回MSVC运行库的主要版本号
def msvc_runtime_major():
    "Return major version of MSVC runtime coded like get_build_msvc_version"
    # 构建一个字典,包含MSVC运行库版本号与主要版本号的对应关系
    major = {1300:  70,  # MSVC 7.0
             1310:  71,  # MSVC 7.1
             1400:  80,  # MSVC 8
             1500:  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格式,返回空列表
    if not f90_ext_match(source):
        return []
    modules = []
    with open(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

# 判断一个对象是否是字符串
def is_string(s):
    return isinstance(s, str)

# 判断列表中的所有项是否都是字符串对象
def all_strings(lst):
    """Return True if all items in lst are string objects. """
    for item in lst:
        if not is_string(item):
            return False
    return True

# 判断一个对象是否是序列(即可迭代的对象,如列表、元组、字符串)
def is_sequence(seq):
    if is_string(seq):
        return False
    try:
        len(seq)
    except Exception:
        return False
    return True

# 判断一个字符串是否是glob模式(带*或?的字符串)
def is_glob_pattern(s):
    return is_string(s) and ('*' in s or '?' in s)

# 将一个对象转换为列表
def as_list(seq):
    if is_sequence(seq):
        return list(seq)
    else:
        return [seq]

# 获取源文件的语言类型
def get_language(sources):
    # not used in numpy/scipy packages, use build_ext.detect_language instead
    """Determine language value (c,f77,f90) from sources """
    language = None
    # 遍历给定的源列表 sources
    for source in sources:
        # 检查当前源是否是字符串类型
        if isinstance(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,否则返回 False
def has_f_sources(sources):
    for source in sources:
        # 调用 fortran_ext_match 函数检查文件名是否匹配 Fortran 文件扩展名
        if fortran_ext_match(source):
            return True
    return False

# 检查给定的源文件列表中是否包含 C++ 文件,如果有则返回 True,否则返回 False
def has_cxx_sources(sources):
    for source in sources:
        # 调用 cxx_ext_match 函数检查文件名是否匹配 C++ 文件扩展名
        if cxx_ext_match(source):
            return True
    return False

# 对给定的源文件列表进行过滤,返回四个文件名列表:C 文件、C++ 文件、Fortran 文件、Fortran 90 模块文件
def filter_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] != '' and not 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

# 分析源文件列表中的包含语句,获取所有被包含的头文件,并返回一个包含这些头文件名的列表
def get_dependencies(sources):
    # 调用 _get_directories 函数获取源文件列表中所有文件的父目录列表,然后调用 _get_headers 获取这些目录中的头文件
    return _get_headers(_get_directories(sources))

# 检查目录是否是本地目录,并返回 True 或 False
def is_local_src_dir(directory):
    if not is_string(directory):
        return False
    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 and not new_dir[0]:
        new_dir = new_dir[1:]
    if new_dir and new_dir[0]=='build':
        return False
    new_dir = os.sep.join(new_dir)
    return os.path.isdir(new_dir)

# 生成指定路径下的源文件列表,排除特定的目录和文件类型,使用生成器实现
def general_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 not in pruned_directories ]
        dirnames[:] = pruned
        for f in filenames:
            # 排除指定文件类型的文件,并生成文件的完整路径
            if not prune_file_pat.search(f):
                yield os.path.join(dirpath, f)

# 生成指定路径下的源文件目录列表和文件列表,排除特定的目录和文件类型,使用生成器实现
def general_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 not in 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) and not 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) if not prune_file_pat.search(f)]
    # 过滤出 filenames 中真正的文件路径(不是目录路径)
    files = [f for f in filenames if os.path.isfile(f)]
    # 生成相对路径 rpath 和文件列表 files 的元组
    yield rpath, files
# 返回具有指定扩展名的源文件及同一目录中的任何包含文件
def get_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

# 获取脚本文件
def get_script_files(scripts):
    # 获取所有脚本文件并返回
    scripts = [_m for _m in scripts if is_string(_m)]
    return scripts

# 返回库的源文件
def get_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

# 获取共享库的扩展名
def get_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扩展,则根据操作系统返回正确的共享库扩展名
    if not 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

# 获取数据文件
def get_data_files(data):
    # 如果数据是字符串,则返回其列表
    if is_string(data):
        return [data]
    # 否则,获取数据源并创建空文件名列表
    sources = data[1]
    filenames = []
    # 对于给定的源列表中的每个元素进行迭代
    for s in sources:
        # 检查当前元素是否是可调用的对象,如果是,则跳过本次迭代
        if hasattr(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
def dot_join(*args):
    # 将传入的参数按照"."连接成一个字符串
    return '.'.join([a for a in args if a])

def get_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 _ in range(level+1):
            frame = frame.f_back
        return frame


######################

class Configuration:

    _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 = []

    def todict(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

    def info(self, message):
        if not self.options['quiet']:
            # 如果选项中的quiet值为False,则输出消息
            print(message)

    def warn(self, message):
        sys.stderr.write('Warning: %s\n' % (message,))
        # 输出警告信息到标准错误流

    def set_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)

    def get_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:
            if not os.path.isfile(njoin(d, '__init__.py')):
                continue
            if '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 属性
            if not hasattr(setup_module, 'configuration'):
                # 如果不假设默认配置,则警告
                if not 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_path
                if 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
    # 返回子包的配置列表
    def get_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 is None:
            if subpackage_path is None:
                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 is None and '*' in subpackage_name:
            return self._wildcard_get_subpackage(subpackage_name,
                                                 parent_name,
                                                 caller_level = caller_level+1)
        assert '*' not in subpackage_name, repr((subpackage_name, subpackage_path, parent_name))
        if subpackage_path is None:
            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)
        if not self.options['ignore_setup_xxx_py']:
            if not os.path.isfile(setup_py):
                setup_py = njoin(subpackage_path,
                                 'setup_%s.py' % (subpackage_name))
        if not os.path.isfile(setup_py):
            if not 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 实例
    def add_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 = None
        else:
            parent_name = self.name
        config_list = self.get_subpackage(subpackage_name, subpackage_path,
                                          parent_name = parent_name,
                                          caller_level = 2)
        if not config_list:
            self.warn('No configuration returned, assuming unavailable.')
        for config in config_list:
            d = config
            if isinstance(config, Configuration):
                d = config.todict()
            assert isinstance(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 is not None:
            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 not in 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
    
    # 添加宏定义到配置
    def add_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 is not None:
            if not hasattr(dist, 'define_macros'):
                dist.define_macros = []
            dist.define_macros.extend(macros)
        else:
            self.define_macros.extend(macros)
    # 将给定的路径添加到配置的包含目录中
    def add_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 is not None:
            if dist.include_dirs is None:
                dist.include_dirs = []
            dist.include_dirs.extend(include_dirs)
        # 如果没有分发对象,则将包含目录添加到当前对象中
        else:
            self.include_dirs.extend(include_dirs)
    
    # 将可安装的头文件添加到配置中
    def add_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:
                if not isinstance(path, (tuple, list)) or len(path) != 2:
                    raise TypeError(repr(path))
                [headers.append((path[0], p)) for p in self.paths(path[1])]
        # 获取当前包的分发对象
        dist = self.get_distribution()
        # 如果有分发对象,则将头文件添加到分发对象中
        if dist is not None:
            if dist.headers is None:
                dist.headers = []
            dist.headers.extend(headers)
        # 如果没有分发对象,则将头文件添加到当前对象中
        else:
            self.headers.extend(headers)
    
    # 将路径应用于全局路径,并添加本地路径(如果需要的话)
    def paths(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
    # 将库添加到配置中
    def add_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 is not None:
            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

        # 有时候,依赖关系默认不为空列表,如果未给出依赖关系,则添加一个空列表
        if not 'depends' in build_info:
            build_info['depends'] = []

        # 修正路径字典
        self._fix_paths_dict(build_info)

        # 将库添加到库列表中,以便与 build_clib 一起构建
        self.libraries.append((name, build_info))
    # 定义一个方法,用于添加已安装的库
    def add_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).
    
        """
        # 如果未提供构建信息,将构建信息设为空字典
        if not 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))
    
    # 定义一个方法,用于添加脚本文件
    def add_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 is not None:
            # 如果发行版的脚本列表为None,则将其设为空列表
            if dist.scripts is None:
                dist.scripts = []
            # 将文件列表添加到发行版的脚本列表中
            dist.scripts.extend(scripts)
        else:
            # 如果发行版不存在,则将文件列表添加到当前对象的脚本列表中
            self.scripts.extend(scripts)
    # 在字典属性中添加另一个字典的内容
    def dict_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 in dict.keys():
            # 如果键不在已知的键列表中
            if key not in 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的配置命令实例
    def get_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

    # 返回临时构建文件的路径
    def get_build_temp_dir(self):
        """
        返回一个临时目录的路径,临时文件应该放在其中。
        """
        cmd = get_cmd('build')
        cmd.ensure_finalized()
        return cmd.build_temp

    # 检查Fortran 77编译器的可用性
    def have_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 编译器可用
    def have_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

    # 向扩展或库项目的库和包含路径中添加库
    def append_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
            assert isinstance(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):
            pass
        else:
            # 使用正则表达式匹配 SVN 版本号
            m = re.match(rb'(?P<revision>\d+)', output)
            if m:
                return int(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):
            with open(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:
                    return int(m.group('revision'))
            else:  # 非 xml 格式的文件,检查内容中的 SVN 版本号
                m = re.search(r'dir[\n\r]+(?P<revision>\d+)', fstr)
                if m:
                    return int(m.group('revision'))
        # 如果以上条件都不满足,返回 None
        return None
    # 定义一个方法用于获取指定路径下 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):
            # 如果出现异常(命令执行错误或系统错误),则忽略异常,不做任何操作
            pass
        else:
            # 如果成功获取到输出,使用正则表达式匹配输出中的版本号
            m = re.match(rb'(?P<revision>\d+)', output)
            if m:
                # 如果匹配成功,返回匹配到的版本号转换为整数类型
                return int(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
            # 打开分支信息文件并读取第一行作为当前分支信息
            with open(branch_fn) as f:
                revision0 = f.read().strip()

            # 初始化一个空的分支映射字典
            branch_map = {}
            # 打开分支缓存文件,逐行读取分支和版本信息,构建分支到版本号的映射
            with open(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)

        # 如果没有找到分支信息文件,则返回空值 None
        return None
    def get_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 is not None:
            return version

        # 从版本文件中获取版本号
        if version_file is None:
            files = ['__version__.py',
                     self.name.split('.')[-1]+'_version.py',
                     'version.py',
                     '__svn_version__.py',
                     '__hg_version__.py']
        else:
            files = [version_file]

        # 指定版本变量名
        if version_variable is None:
            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 is None:
                    continue

                # 尝试从模块中获取版本变量
                for a in version_vars:
                    version = getattr(version_module, a, None)
                    if version is not None:
                        break

                # 尝试使用 versioneer 模块获取版本号
                try:
                    version = version_module.get_versions()['version']
                except AttributeError:
                    pass

                if version is not None:
                    break

        # 如果找到版本号,则设置对象属性并返回版本号
        if version is not None:
            self.version = version
            return version

        # 尝试获取 SVN 或 Mercurial 的修订号作为版本号
        revision = self._get_svn_revision(self.local_path)
        if revision is None:
            revision = self._get_hg_revision(self.local_path)

        # 如果获取到修订号,则将其转换为字符串并设置对象属性
        if revision is not None:
            version = str(revision)
            self.version = version

        # 返回最终获取的版本号
        return version
    def make_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 is None:
            return
        else:
            def generate_svn_version_py():
                # 如果目标文件不存在,则创建
                if not os.path.isfile(target):
                    version = str(revision)
                    self.info('Creating %s (version=%r)' % (target, version))
                    # 写入修订版本号到目标文件
                    with open(target, 'w') as f:
                        f.write('version = %r\n' % (version))

                # 定义一个函数,用于删除目标文件及其编译后的版本
                def rm_file(f=target, p=self.info):
                    if delete:
                        try:
                            os.remove(f)
                            p('removed ' + f)
                        except OSError:
                            pass
                        try:
                            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 列表中
    def make_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 is None:
            return
        else:
            # 定义生成 __hg_version__.py 文件的函数
            def generate_hg_version_py():
                # 如果目标文件不存在,则创建文件并写入版本信息
                if not os.path.isfile(target):
                    version = str(revision)
                    self.info('Creating %s (version=%r)' % (target, version))
                    with open(target, 'w') as f:
                        f.write('version = %r\n' % (version))
                
                # 定义删除文件的函数
                def rm_file(f=target, p=self.info):
                    # 如果 delete 标志为 True,则尝试删除目标文件和其对应的编译文件
                    if delete:
                        try: 
                            os.remove(f)
                            p('removed ' + f)
                        except OSError:
                            pass
                        try:
                            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 列表中
    def make_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))

    # 为当前类定义一个方法,用于获取多个资源信息并返回一个包含所有信息的字典
    def get_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
# 根据命令名获取命令对象,使用缓存加速查找
def get_cmd(cmdname, _cache={}):
    if cmdname not in _cache:
        # 导入distutils核心模块
        import distutils.core
        # 获取distutils的设置分发对象
        dist = distutils.core._setup_distribution
        # 如果设置分发对象为None,则抛出内部错误异常
        if dist is None:
            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包含目录
def get_numpy_include_dirs():
    # 复制numpy_include_dirs列表内容
    include_dirs = Configuration.numpy_include_dirs[:]
    # 如果列表为空,则导入numpy模块并获取其包含目录
    if not include_dirs:
        import numpy
        include_dirs = [ numpy.get_include() ]
    # 返回包含目录列表
    return include_dirs

# 获取npy-pkg-config目录路径
def get_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 is not None:
        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

# 获取指定包名的库信息
def get_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库的信息字典
def get_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 in file 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,则抛出异常
    if not compiler.compiler_type == "msvc":
        raise ValueError("Compiler instance is not msvc (%s)"\
                         % compiler.compiler_type)
    # 返回 MSVC 编译器的主要版本和次要版本
    return compiler._MSVCCompiler__version

def get_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'}


def sanitize_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 not in _cxx_ignore_flags]
# 使用 importlib 工具来从指定位置的文件中导入模块 `modname`
def exec_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

.\numpy\numpy\distutils\msvc9compiler.py

import os  # 导入操作系统相关的模块
from distutils.msvc9compiler import MSVCCompiler as _MSVCCompiler  # 导入MSVC编译器相关模块

from .system_info import platform_bits  # 从当前包导入平台位数信息


def _merge(old, new):
    """Concatenate two environment paths avoiding repeats.

    Here `old` is the environment string before the base class initialize
    function is called and `new` is the string after the call. The new string
    will be a fixed string if it is not obtained from the current environment,
    or the same as the old string if obtained from the same environment. The aim
    here is not to append the new string if it is already contained in the old
    string so as to limit the growth of the environment string.

    Parameters
    ----------
    old : string
        Previous environment string.
    new : string
        New environment string.

    Returns
    -------
    ret : string
        Updated environment string.

    """
    if not old:
        return new
    if new in old:
        return old

    # Neither new nor old is empty. Give old priority.
    return ';'.join([old, new])


class MSVCCompiler(_MSVCCompiler):
    def __init__(self, verbose=0, dry_run=0, force=0):
        _MSVCCompiler.__init__(self, verbose, dry_run, force)

    def initialize(self, plat_name=None):
        # The 'lib' and 'include' variables may be overwritten
        # by MSVCCompiler.initialize, so save them for later merge.
        environ_lib = os.getenv('lib')  # 获取环境变量 'lib' 的值
        environ_include = os.getenv('include')  # 获取环境变量 'include' 的值
        _MSVCCompiler.initialize(self, plat_name)  # 调用父类的初始化方法

        # Merge current and previous values of 'lib' and 'include'
        os.environ['lib'] = _merge(environ_lib, os.environ['lib'])  # 合并并更新环境变量 'lib'
        os.environ['include'] = _merge(environ_include, os.environ['include'])  # 合并并更新环境变量 'include'

        # msvc9 building for 32 bits requires SSE2 to work around a
        # compiler bug.
        if platform_bits == 32:  # 如果平台位数为32位
            self.compile_options += ['/arch:SSE2']  # 添加编译选项 '/arch:SSE2'
            self.compile_options_debug += ['/arch:SSE2']  # 添加调试编译选项 '/arch:SSE2'

    def manifest_setup_ldargs(self, output_filename, build_temp, ld_args):
        ld_args.append('/MANIFEST')  # 向链接参数列表中添加 '/MANIFEST'
        _MSVCCompiler.manifest_setup_ldargs(self, output_filename,
                                            build_temp, ld_args)  # 调用父类的方法设置链接参数

.\numpy\numpy\distutils\msvccompiler.py

import os  # 导入操作系统模块
from distutils.msvccompiler import MSVCCompiler as _MSVCCompiler  # 导入 MSVC 编译器类
from .system_info import platform_bits  # 导入平台位数信息


def _merge(old, new):
    """Concatenate two environment paths avoiding repeats.

    Here `old` is the environment string before the base class initialize
    function is called and `new` is the string after the call. The new string
    will be a fixed string if it is not obtained from the current environment,
    or the same as the old string if obtained from the same environment. The aim
    here is not to append the new string if it is already contained in the old
    string so as to limit the growth of the environment string.

    Parameters
    ----------
    old : string
        Previous environment string.
    new : string
        New environment string.

    Returns
    -------
    ret : string
        Updated environment string.

    """
    if new in old:
        return old
    if not old:
        return new

    # Neither new nor old is empty. Give old priority.
    return ';'.join([old, new])


class MSVCCompiler(_MSVCCompiler):
    def __init__(self, verbose=0, dry_run=0, force=0):
        _MSVCCompiler.__init__(self, verbose, dry_run, force)

    def initialize(self):
        # The 'lib' and 'include' variables may be overwritten
        # by MSVCCompiler.initialize, so save them for later merge.
        environ_lib = os.getenv('lib', '')  # 获取环境变量 'lib',默认为空字符串
        environ_include = os.getenv('include', '')  # 获取环境变量 'include',默认为空字符串
        _MSVCCompiler.initialize(self)  # 调用父类的初始化方法

        # Merge current and previous values of 'lib' and 'include'
        os.environ['lib'] = _merge(environ_lib, os.environ['lib'])  # 合并并更新 'lib' 环境变量
        os.environ['include'] = _merge(environ_include, os.environ['include'])  # 合并并更新 'include' 环境变量

        # msvc9 building for 32 bits requires SSE2 to work around a
        # compiler bug.
        if platform_bits == 32:  # 如果平台位数是 32 位
            self.compile_options += ['/arch:SSE2']  # 添加 SSE2 选项到编译选项
            self.compile_options_debug += ['/arch:SSE2']  # 添加 SSE2 选项到调试编译选项


def lib_opts_if_msvc(build_cmd):
    """ Add flags if we are using MSVC compiler

    We can't see `build_cmd` in our scope, because we have not initialized
    the distutils build command, so use this deferred calculation to run
    when we are building the library.
    """
    if build_cmd.compiler.compiler_type != 'msvc':  # 如果编译器类型不是 MSVC
        return []  # 返回空列表

    # Explicitly disable whole-program optimization.
    flags = ['/GL-']  # 设置禁用整体程序优化的标志

    # Disable voltbl section for vc142 to allow link using mingw-w64; see:
    # https://github.com/matthew-brett/dll_investigation/issues/1#issuecomment-1100468171
    if build_cmd.compiler_opt.cc_test_flags(['-d2VolatileMetadata-']):  # 如果编译器支持 '-d2VolatileMetadata-' 标志
        flags.append('-d2VolatileMetadata-')  # 添加此标志到编译选项

    return flags  # 返回标志列表

.\numpy\numpy\distutils\npy_pkg_config.py

import sys
import re
import os

from configparser import RawConfigParser

__all__ = ['FormatError', 'PkgNotFound', 'LibraryInfo', 'VariableSet',
        'read_config', 'parse_flags']

# 正则表达式模式,用于匹配形如 "${...}" 的变量格式
_VAR = re.compile(r'\$\{([a-zA-Z0-9_-]+)\}')

class FormatError(OSError):
    """
    Exception thrown when there is a problem parsing a configuration file.
    
    Attributes:
        msg (str): Error message describing the problem.
    """
    def __init__(self, msg):
        self.msg = msg

    def __str__(self):
        return self.msg

class PkgNotFound(OSError):
    """
    Exception raised when a package cannot be located.
    
    Attributes:
        msg (str): Error message indicating which package could not be found.
    """
    def __init__(self, msg):
        self.msg = msg

    def __str__(self):
        return self.msg

def parse_flags(line):
    """
    Parse a line from a config file containing compile flags.

    Parameters
    ----------
    line : str
        A single line containing one or more compile flags.

    Returns
    -------
    d : dict
        Dictionary of parsed flags, split into relevant categories.
        These categories are the keys of `d`:

        * 'include_dirs'
        * 'library_dirs'
        * 'libraries'
        * 'macros'
        * 'ignored'

    """
    d = {'include_dirs': [], 'library_dirs': [], 'libraries': [],
         'macros': [], 'ignored': []}

    # Split the line by ' -' to isolate individual flags
    flags = (' ' + line).split(' -')
    for flag in flags:
        flag = '-' + flag
        if len(flag) > 0:
            # Categorize flags based on their type
            if flag.startswith('-I'):
                d['include_dirs'].append(flag[2:].strip())
            elif flag.startswith('-L'):
                d['library_dirs'].append(flag[2:].strip())
            elif flag.startswith('-l'):
                d['libraries'].append(flag[2:].strip())
            elif flag.startswith('-D'):
                d['macros'].append(flag[2:].strip())
            else:
                d['ignored'].append(flag)

    return d

def _escape_backslash(val):
    """
    Escape backslashes in the given string.

    Parameters
    ----------
    val : str
        Input string where backslashes need to be escaped.

    Returns
    -------
    str
        String with backslashes escaped as '\\\\'.
    """
    return val.replace('\\', '\\\\')

class LibraryInfo:
    """
    Object containing build information about a library.

    Parameters
    ----------
    name : str
        The library name.
    description : str
        Description of the library.
    version : str
        Version string.
    sections : dict
        The sections of the configuration file for the library. The keys are
        the section headers, the values the text under each header.
    vars : class instance
        A `VariableSet` instance, which contains ``(name, value)`` pairs for
        variables defined in the configuration file for the library.
    requires : sequence, optional
        The required libraries for the library to be installed.

    Notes
    -----
    All input parameters (except "sections" which is a method) are available as
    attributes of the same name.

    """
    # 初始化函数,用于创建一个配置对象
    def __init__(self, name, description, version, sections, vars, requires=None):
        # 设置配置对象的名称
        self.name = name
        # 设置配置对象的描述
        self.description = description
        # 如果指定了requires,则使用指定的requires,否则使用空列表
        if requires:
            self.requires = requires
        else:
            self.requires = []
        # 设置配置对象的版本号
        self.version = version
        # 设置配置对象的私有属性_sections,用于存储配置文件的各个部分
        self._sections = sections
        # 设置配置对象的vars,用于变量插值
        self.vars = vars

    # 返回配置文件中所有部分的头信息(section headers)
    def sections(self):
        """
        Return the section headers of the config file.

        Parameters
        ----------
        None

        Returns
        -------
        keys : list of str
            The list of section headers.

        """
        # 返回所有部分的键(即部分头信息)
        return list(self._sections.keys())

    # 获取指定部分(section)的cflags配置,进行变量插值后返回
    def cflags(self, section="default"):
        # 获取指定部分的cflags配置值,并进行变量插值
        val = self.vars.interpolate(self._sections[section]['cflags'])
        # 对返回的值进行反斜杠转义处理
        return _escape_backslash(val)

    # 获取指定部分(section)的libs配置,进行变量插值后返回
    def libs(self, section="default"):
        # 获取指定部分的libs配置值,并进行变量插值
        val = self.vars.interpolate(self._sections[section]['libs'])
        # 对返回的值进行反斜杠转义处理
        return _escape_backslash(val)

    # 返回配置对象的字符串表示形式,包括名称、描述、依赖和版本信息
    def __str__(self):
        # 初始化信息列表
        m = ['Name: %s' % self.name, 'Description: %s' % self.description]
        # 如果有依赖信息,则加入依赖信息到列表
        if self.requires:
            m.append('Requires:')
        else:
            # 如果没有依赖信息,则加入空的Requires信息到列表
            m.append('Requires: %s' % ",".join(self.requires))
        # 加入版本信息到列表
        m.append('Version: %s' % self.version)

        # 返回以换行符连接的信息列表作为对象的字符串表示形式
        return "\n".join(m)
class VariableSet:
    """
    Container object for the variables defined in a config file.

    `VariableSet` can be used as a plain dictionary, with the variable names
    as keys.

    Parameters
    ----------
    d : dict
        Dict of items in the "variables" section of the configuration file.

    """
    def __init__(self, d):
        # Initialize with a copy of the provided dictionary
        self._raw_data = dict([(k, v) for k, v in d.items()])

        # Initialize dictionaries for regular expressions and substitution values
        self._re = {}
        self._re_sub = {}

        # Call the initialization method to parse variables
        self._init_parse()

    def _init_parse(self):
        # Iterate over each key-value pair in the raw data dictionary
        for k, v in self._raw_data.items():
            # Initialize a regular expression for variable interpolation
            self._init_parse_var(k, v)

    def _init_parse_var(self, name, value):
        # Compile a regular expression for ${name} and store the substitution value
        self._re[name] = re.compile(r'\$\{%s\}' % name)
        self._re_sub[name] = value

    def interpolate(self, value):
        # Brute force interpolation method to substitute variables in 'value'
        def _interpolate(value):
            for k in self._re.keys():
                value = self._re[k].sub(self._re_sub[k], value)
            return value
        
        # Continue interpolating until no more ${var} patterns are found or until stable
        while _VAR.search(value):
            nvalue = _interpolate(value)
            if nvalue == value:
                break
            value = nvalue

        return value

    def variables(self):
        """
        Return the list of variable names.

        Parameters
        ----------
        None

        Returns
        -------
        names : list of str
            The names of all variables in the `VariableSet` instance.

        """
        return list(self._raw_data.keys())

    # Emulate a dict to set/get variables values
    def __getitem__(self, name):
        return self._raw_data[name]

    def __setitem__(self, name, value):
        # Set a variable value and reinitialize its regular expression and substitution
        self._raw_data[name] = value
        self._init_parse_var(name, value)

def parse_meta(config):
    # Check if 'meta' section exists in the configuration
    if not config.has_section('meta'):
        raise FormatError("No meta section found !")

    # Create a dictionary from items in the 'meta' section
    d = dict(config.items('meta'))

    # Ensure mandatory keys ('name', 'description', 'version') exist in 'meta'
    for k in ['name', 'description', 'version']:
        if not k in d:
            raise FormatError("Option %s (section [meta]) is mandatory, "
                "but not found" % k)

    # If 'requires' key is missing, initialize it as an empty list
    if not 'requires' in d:
        d['requires'] = []

    return d

def parse_variables(config):
    # Check if 'variables' section exists in the configuration
    if not config.has_section('variables'):
        raise FormatError("No variables section found !")

    # Initialize an empty dictionary for variables
    d = {}

    # Populate the dictionary with items from the 'variables' section
    for name, value in config.items("variables"):
        d[name] = value

    # Return a VariableSet object initialized with the variables dictionary
    return VariableSet(d)

def parse_sections(config):
    return meta_d, r

def pkg_to_filename(pkg_name):
    # Convert package name to a filename by appending '.ini'
    return "%s.ini" % pkg_name

def parse_config(filename, dirs=None):
    # If directories are provided, create a list of filenames with path
    if dirs:
        filenames = [os.path.join(d, filename) for d in dirs]
    else:
        filenames = [filename]

    # Initialize a RawConfigParser object
    config = RawConfigParser()

    # Read configuration files specified in 'filenames'
    n = config.read(filenames)

    # If no files were successfully read, raise PkgNotFound exception
    if not len(n) >= 1:
        raise PkgNotFound("Could not find file(s) %s" % str(filenames))

    # Parse 'meta' section of the configuration
    meta = parse_meta(config)

    # Initialize an empty dictionary for variables
    vars = {}
    # 检查配置文件中是否存在 'variables' 这个部分
    if config.has_section('variables'):
        # 遍历 'variables' 部分的每个键值对,将值经过 _escape_backslash 函数处理后存入 vars 字典中
        for name, value in config.items("variables"):
            vars[name] = _escape_backslash(value)

    # 解析除了 'meta' 和 'variables' 以外的普通部分
    secs = [s for s in config.sections() if not s in ['meta', 'variables']]
    sections = {}

    # 创建一个空的 requires 字典,用于存储每个部分可能的 'requires' 值
    requires = {}

    # 遍历每个普通部分(不包括 'meta' 和 'variables')
    for s in secs:
        d = {}

        # 如果当前部分有 'requires' 选项,则将其存入 requires 字典中
        if config.has_option(s, "requires"):
            requires[s] = config.get(s, 'requires')

        # 遍历当前部分的所有键值对,存入字典 d 中
        for name, value in config.items(s):
            d[name] = value

        # 将当前部分的字典 d 存入 sections 字典中
        sections[s] = d

    # 返回解析得到的 meta 信息、vars 变量字典、sections 部分字典和 requires 要求字典
    return meta, vars, sections, requires
def _read_config_imp(filenames, dirs=None):
    # 定义内部函数 _read_config,用于读取配置文件并返回元数据、变量、部分和要求的字典
    def _read_config(f):
        # 解析配置文件 f,获取元数据、变量、部分和要求的字典
        meta, vars, sections, reqs = parse_config(f, dirs)
        
        # 递归添加所需库的部分和变量
        for rname, rvalue in reqs.items():
            # 递归调用 _read_config 获取所需库的元数据、变量、部分和要求的字典
            nmeta, nvars, nsections, nreqs = _read_config(pkg_to_filename(rvalue))

            # 更新变量字典,添加不在 'top' 配置文件中的变量
            for k, v in nvars.items():
                if k not in vars:
                    vars[k] = v

            # 更新部分字典
            for oname, ovalue in nsections[rname].items():
                if ovalue:
                    sections[rname][oname] += ' %s' % ovalue

        # 返回解析得到的元数据、变量、部分和要求的字典
        return meta, vars, sections, reqs

    # 调用内部函数 _read_config 解析配置文件列表 filenames,并获取元数据、变量、部分和要求的字典
    meta, vars, sections, reqs = _read_config(filenames)

    # FIXME: document this. If pkgname is defined in the variables section, and
    # there is no pkgdir variable defined, pkgdir is automatically defined to
    # the path of pkgname. This requires the package to be imported to work
    # 如果在变量部分定义了 pkgname,并且没有定义 pkgdir 变量,则自动将 pkgdir 定义为 pkgname 的路径。
    # 这要求导入该包才能正常工作。
    if 'pkgdir' not in vars and "pkgname" in vars:
        pkgname = vars["pkgname"]
        # 如果 pkgname 没有在 sys.modules 中注册(未被导入)
        if pkgname not in sys.modules:
            # 抛出值错误异常,要求导入该包以获取信息
            raise ValueError("You should import %s to get information on %s" %
                             (pkgname, meta["name"]))

        # 获取 pkgname 对应的模块对象
        mod = sys.modules[pkgname]
        # 设置 vars 字典中的 pkgdir 变量为模块文件所在目录的转义反斜杠路径
        vars["pkgdir"] = _escape_backslash(os.path.dirname(mod.__file__))

    # 返回 LibraryInfo 类的实例,包含元数据的名称、描述、版本、部分和变量集
    return LibraryInfo(name=meta["name"], description=meta["description"],
            version=meta["version"], sections=sections, vars=VariableSet(vars))

# 简单缓存,用于缓存 LibraryInfo 实例的创建。为了真正高效,缓存应该在 read_config 中处理,
# 因为同一个文件可以在 LibraryInfo 创建之外多次解析,但我怀疑在实践中这不会是问题
_CACHE = {}
def read_config(pkgname, dirs=None):
    """
    从配置文件中返回包的库信息。

    Parameters
    ----------
    pkgname : str
        包的名称(应该与 .ini 文件的名称匹配,不包括扩展名,例如 foo 对应 foo.ini)。
    dirs : sequence, optional
        如果给定,应该是一个目录序列 - 通常包括 NumPy 基础目录 - 用于查找 npy-pkg-config 文件。

    Returns
    -------
    pkginfo : class instance
        包含构建信息的 `LibraryInfo` 实例。

    Raises
    ------
    PkgNotFound
        如果找不到包。

    See Also
    --------
    misc_util.get_info, misc_util.get_pkg_info

    Examples
    --------
    >>> npymath_info = np.distutils.npy_pkg_config.read_config('npymath')
    >>> type(npymath_info)
    <class 'numpy.distutils.npy_pkg_config.LibraryInfo'>
    >>> print(npymath_info)
    Name: npymath
    Description: Portable, core math library implementing C99 standard
    Requires:
    Version: 0.1  #random

    """
    try:
        return _CACHE[pkgname]
    # 如果发生 KeyError 异常,则执行以下操作
    v = _read_config_imp(pkg_to_filename(pkgname), dirs)
    # 将返回的配置信息存入缓存字典中,键为 pkgname
    _CACHE[pkgname] = v
    # 返回获取的配置信息
    return v
# TODO:
#   - implements version comparison (modversion + atleast)

# pkg-config simple emulator - useful for debugging, and maybe later to query
# the system
if __name__ == '__main__':
    from optparse import OptionParser  # 导入选项解析器类
    import glob  # 导入用于文件路径模式匹配的 glob 模块

    parser = OptionParser()  # 创建选项解析器对象
    parser.add_option("--cflags", dest="cflags", action="store_true",
                      help="output all preprocessor and compiler flags")  # 添加选项:输出所有预处理器和编译器标志
    parser.add_option("--libs", dest="libs", action="store_true",
                      help="output all linker flags")  # 添加选项:输出所有链接器标志
    parser.add_option("--use-section", dest="section",
                      help="use this section instead of default for options")  # 添加选项:使用指定的部分而不是默认部分
    parser.add_option("--version", dest="version", action="store_true",
                      help="output version")  # 添加选项:输出版本号
    parser.add_option("--atleast-version", dest="min_version",
                      help="Minimal version")  # 添加选项:至少指定的版本号
    parser.add_option("--list-all", dest="list_all", action="store_true",
                      help="Minimal version")  # 添加选项:列出所有信息
    parser.add_option("--define-variable", dest="define_variable",
                      help="Replace variable with the given value")  # 添加选项:用给定值替换变量

    (options, args) = parser.parse_args(sys.argv)  # 解析命令行参数

    if len(args) < 2:
        raise ValueError("Expect package name on the command line:")  # 如果命令行参数少于两个,抛出值错误异常

    if options.list_all:
        files = glob.glob("*.ini")  # 获取当前目录下所有以 .ini 结尾的文件列表
        for f in files:
            info = read_config(f)  # 读取配置文件信息
            print("%s\t%s - %s" % (info.name, info.name, info.description))  # 打印格式化输出:名称、名称和描述

    pkg_name = args[1]  # 获取命令行中的第二个参数作为包名
    d = os.environ.get('NPY_PKG_CONFIG_PATH')  # 获取环境变量 NPY_PKG_CONFIG_PATH 的值
    if d:
        info = read_config(
            pkg_name, ['numpy/_core/lib/npy-pkg-config', '.', d]
        )  # 读取配置信息,优先使用指定路径
    else:
        info = read_config(
            pkg_name, ['numpy/_core/lib/npy-pkg-config', '.']
        )  # 读取配置信息,默认搜索当前目录

    if options.section:
        section = options.section  # 如果指定了 --use-section 选项,则使用指定的部分
    else:
        section = "default"  # 否则使用默认部分

    if options.define_variable:
        m = re.search(r'([\S]+)=([\S]+)', options.define_variable)  # 匹配 --define-variable 选项的值格式
        if not m:
            raise ValueError("--define-variable option should be of "
                             "the form --define-variable=foo=bar")  # 如果格式不正确,抛出值错误异常
        else:
            name = m.group(1)  # 获取变量名
            value = m.group(2)  # 获取变量值
        info.vars[name] = value  # 将变量名和值存入配置信息的变量字典中

    if options.cflags:
        print(info.cflags(section))  # 如果指定了 --cflags 选项,则打印预处理器和编译器标志
    if options.libs:
        print(info.libs(section))  # 如果指定了 --libs 选项,则打印链接器标志
    if options.version:
        print(info.version)  # 如果指定了 --version 选项,则打印版本号
    if options.min_version:
        print(info.version >= options.min_version)  # 如果指定了 --atleast-version 选项,则打印是否当前版本号不小于指定版本号