Análise do Código Fonte do NumPy: Compiladores Fortran

Este artigo detalha a implementação de compiladores Fortran dentro do subsistema numpy.distutils, focando em como diferentes compiladores e suas configurações são gerenciadso.

# .\numpy\numpy\distutils\fcompiler\sun.py
from numpy.distutils.ccompiler import simple_version_match
from numpy.distutils.fcompiler import FCompiler

compilers = ['SunFCompiler']

class SunFCompiler(FCompiler):
    compiler_type = 'sun'
    description = 'Sun or Forte Fortran 95 Compiler'
    version_match = simple_version_match(
                      start=r'f9[05]: (Sun|Forte|WorkShop).*Fortran 95')

    executables = {
        'version_cmd'  : ["<F90>", "-V"],
        'compiler_f77' : ["f90"],
        'compiler_fix' : ["f90", "-fixed"],
        'compiler_f90' : ["f90"],
        'linker_so'    : ["<F90>", "-Bdynamic", "-G"],
        'archiver'     : ["ar", "-cr"],
        'ranlib'       : ["ranlib"]
        }
    
    module_dir_switch = '-moddir='
    module_include_switch = '-M'
    pic_flags = ['-xcode=pic32']

    def get_flags_f77(self):
        ret = ["-ftrap=%none"]
        if (self.get_version() or '') >= '7':
            ret.append("-f77")
        else:
            ret.append("-fixed")
        return ret
    
    def get_opt(self):
        return ['-fast', '-dalign']
    
    def get_arch(self):
        return ['-xtarget=generic']
    
    def get_libraries(self):
        opt = []
        opt.extend(['fsu', 'sunmath', 'mvec'])
        return opt
    
    def runtime_library_dir_option(self, dir):
        return '-R%s' % dir

if __name__ == '__main__':
    from distutils import log
    log.set_verbosity(2)
    from numpy.distutils import customized_fcompiler
    print(customized_fcompiler(compiler='sun').get_version())

O arquivo sun.py define o compilador SunFCompiler, que é usado para compilar código Fortran com compiladores Sun ou Forte. Ele especifica os comandos executáveis, flags de compilação e opções de biblioteca específicas para este compilador.

# .\numpy\numpy\distutils\fcompiler\vast.py
import os

from numpy.distutils.fcompiler.gnu import GnuFCompiler

compilers = ['VastFCompiler']

class VastFCompiler(GnuFCompiler):
    compiler_type = 'vast'
    compiler_aliases = ()
    description = 'Pacific-Sierra Research Fortran 90 Compiler'
    version_pattern = (r'\s*Pacific-Sierra Research vf90 '
                       r'(Personal|Professional)\s+(?P<version>[^\s]*)')

    object_switch = ' && function _mvfile { mv -v `basename $1` $1 ; } && _mvfile '

    executables = {
        'version_cmd'  : ["vf90", "-v"],
        'compiler_f77' : ["g77"],
        'compiler_fix' : ["f90", "-Wv,-ya"],
        'compiler_f90' : ["f90"],
        'linker_so'    : ["<F90>"],
        'archiver'     : ["ar", "-cr"],
        'ranlib'       : ["ranlib"]
        }

    module_dir_switch = None
    module_include_switch = None

    def find_executables(self):
        pass

    def get_version_cmd(self):
        f90 = self.compiler_f90[0]
        d, b = os.path.split(f90)
        vf90 = os.path.join(d, 'v'+b)
        return vf90

    def get_flags_arch(self):
        vast_version = self.get_version()
        gnu = GnuFCompiler()
        gnu.customize(None)
        self.version = gnu.get_version()
        opt = GnuFCompiler.get_flags_arch(self)
        self.version = vast_version
        return opt

if __name__ == '__main__':
    from distutils import log
    log.set_verbosity(2)
    from numpy.distutils import customized_fcompiler
    print(customized_fcompiler(compiler='vast').get_version())

O arquivo vast.py implementa o compilador VastFCompiler, que é uma variante do compilador GNU Fortran. Ele lida com a particularidade de que o compilador VAST F90 não suporta o uso conjunto de -o e -c, movendo arquivos objeto criados no diretório atual para o diretório de compilação.

# .\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

from distutils.sysconfig import get_python_lib
from distutils.fancy_getopt import FancyGetopt
from distutils.errors import DistutilsModuleError, \
     DistutilsExecError, CompileError, LinkError, DistutilsPlatformError
from distutils.util import split_quoted, strtobool

from numpy.distutils.ccompiler import CCompiler, gen_lib_options
from numpy.distutils import log
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

from .environment import EnvironmentConfig

__metaclass__ = type

FORTRAN_COMMON_FIXED_EXTENSIONS = ['.for', '.ftn', '.f77', '.f']

class CompilerNotFound(Exception):
    pass

def flaglist(s):
    if is_string(s):
        return split_quoted(s)
    else:
        return s

def str2bool(s):
    if is_string(s):
        return strtobool(s)
    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
    """
    
    distutils_vars = EnvironmentConfig(
        distutils_section='config_fc',
        noopt = (None, None, 'noopt', str2bool, False),
        noarch = (None, None, 'noarch', str2bool, False),
        debug = (None, None, 'debug', str2bool, False),
        verbose = (None, None, 'verbose', str2bool, False),
    )
    
    command_vars = EnvironmentConfig(
        distutils_section='config_fc',
        compiler_f77 = ('exe.compiler_f77', 'F77', 'f77exec', None, False),
        compiler_f90 = ('exe.compiler_f90', 'F90', 'f90exec', None, False),
        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),
    )
    
    flag_vars = EnvironmentConfig(
        distutils_section='config_fc',
        f77 = ('flags.f77', 'F77FLAGS', 'f77flags', flaglist, True),
        f90 = ('flags.f90', 'F90FLAGS', 'f90flags', flaglist, True),
        free = ('flags.free', 'FREEFLAGS', 'freeflags', flaglist, True),
        fix = ('flags.fix', None, None, flaglist, False),
        opt = ('flags.opt', 'FOPT', 'opt', flaglist, True),
        opt_f77 = ('flags.opt_f77', None, None, flaglist, False),
        opt_f90 = ('flags.opt_f90', None, None, flaglist, False),
        arch = ('flags.arch', 'FARCH', 'arch', flaglist, False),
        arch_f77 = ('flags.arch_f77', None, None, flaglist, False),
        arch_f90 = ('flags.arch_f90', None, None, flaglist, False),
        debug = ('flags.debug', 'FDEBUG', 'fdebug', flaglist, True),
        debug_f77 = ('flags.debug_f77', None, None, flaglist, False),
        debug_f90 = ('flags.debug_f90', None, None, flaglist, False),
        flags = ('self.get_flags', 'FFLAGS', 'fflags', flaglist, True),
        linker_so = ('flags.linker_so', 'LDFLAGS', 'ldflags', flaglist, True),
        linker_exe = ('flags.linker_exe', 'LDFLAGS', 'ldflags', flaglist, True),
        ar = ('flags.ar', 'ARFLAGS', 'arflags', flaglist, True),
    )
    
    language_map = {'.f': 'f77',
                    '.for': 'f77',
                    '.F': 'f77',
                    '.ftn': 'f77',
                    '.f77': 'f77',
                    '.f90': 'f90',
                    '.F90': 'f90',
                    '.f95': 'f90',
                    }
    
    language_order = ['f90', 'f77']
    
    compiler_type = None
    compiler_aliases = ()
    version_pattern = None

    possible_executables = []
    executables = {
        'version_cmd': ["f77", "-v"],
        'compiler_f77': ["f77"],
        'compiler_f90': ["f90"],
        'compiler_fix': ["f90", "-fixed"],
        'linker_so': ["f90", "-shared"],
        'linker_exe': ["f90"],
        'archiver': ["ar", "-cr"],
        'ranlib': None,
        }

    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"
    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']

    c_compiler = None

    extra_f77_compile_args = []
    extra_f90_compile_args = []

    def __init__(self, *args, **kw):
        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()

        for e in self._executable_keys:
            if e not in self.executables:
                self.executables[e] = None

        self._is_customised = False
    
    def __copy__(self):
        obj = self.__new__(self.__class__)
        obj.__dict__.update(self.__dict__)
        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)
        obj.executables = obj.executables.copy()
        return obj

    def copy(self):
        return self.__copy__()

    def _command_property(key):
        def fget(self):
            assert self._is_customised
            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):
        if not key in self._executable_keys:
            raise ValueError(
                "unknown executable '%s' for class %s" %
                (key, self.__class__.__name__))
        if is_string(value):
            value = split_quoted(value)
        assert value is None or is_sequence_of_strings(value[1:]), (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):
        pass

    def get_flags(self):
        return [] + self.pic_flags

    def _get_command_flags(self, key):
        cmd = self.executables.get(key, None)
        if cmd is None:
            return []
        return cmd[1:]

    def get_flags_f77(self):
        return self._get_command_flags('compiler_f77')
    
    def get_flags_f90(self):
        return self._get_command_flags('compiler_f90')

    def get_flags_free(self):
        return []

    def get_flags_fix(self):
        return self._get_command_flags('compiler_fix')

    def get_flags_linker_so(self):
        return self._get_command_flags('linker_so')

    def get_flags_linker_exe(self):
        return self._get_command_flags('linker_exe')

    def get_flags_ar(self):
        return self._get_command_flags('archiver')

    def get_flags_opt(self):
        return []

    def get_flags_arch(self):
        return []

    def get_flags_debug(self):
        return []

    get_flags_opt_f77 = get_flags_opt_f90 = get_flags_opt

    get_flags_arch_f77 = get_flags_arch_f90 = get_flags_arch

    get_flags_debug_f77 = get_flags_debug_f90 = get_flags_debug

    def get_libraries(self):
        return self.libraries[:]

    def get_library_dirs(self):
        return self.library_dirs[:]

    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):
        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()

        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):
        src_flags = {}
        if Path(src).suffix.lower() in FORTRAN_COMMON_FIXED_EXTENSIONS \
           and not has_f90_header(src):
            flavor = ':f77'
            compiler = self.compiler_f77
            src_flags = get_f77flags(src)
            extra_compile_args = self.extra_f77_compile_args or []
        
        elif is_free_format(src):
            flavor = ':f90'
            compiler = self.compiler_f90
            if compiler is None:
                raise DistutilsExecError('f90 not supported by %s needed for %s'\
                      % (self.__class__.__name__, src))
            extra_compile_args = self.extra_f90_compile_args or []
        
        else:
            flavor = ':fix'
            compiler = self.compiler_fix
            if compiler is None:
                raise DistutilsExecError('f90 (fixed) not supported by %s needed for %s'\
                      % (self.__class__.__name__, src))
            extra_compile_args = self.extra_f90_compile_args or []
        
        if self.object_switch[-1]==' ':
            o_args = [self.object_switch.strip(), obj]
        else:
            o_args = [self.object_switch.strip()+obj]
        
        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:
            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

    def library_option(self, lib):
        return "-l" + lib
    
    def library_dir_option(self, dir):
        return "-L" + dir
    
    def link(self, objects, output_filename, output_dir=None,
             libraries=None, library_dirs=None, runtime_library_dirs=None,
             extra_preargs=None, extra_postargs=None, debug=False,
             target_desc=CCompiler.EXECUTABLE):
        objects, output_dir = self._fix_object_args(objects, output_dir)
        
        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)
        
        if is_string(output_dir):
            output_filename = os.path.join(output_dir, output_filename)
        elif output_dir is not None:
            raise TypeError("'output_dir' must be a string or None")
        
        if self._need_link(objects, output_filename):
            if self.library_switch[-1]==' ':
                o_args = [self.library_switch.strip(), output_filename]
            else:
                o_args = [self.library_switch.strip()+output_filename]
            
            if is_string(self.objects):
                ld_args = objects + [self.objects]
            else:
                ld_args = objects + self.objects
            
            ld_args = ld_args + lib_opts + o_args
            
            if debug:
                ld_args[:0] = ['-g']
            
            if extra_preargs:
                ld_args[:0] = extra_preargs
            
            if extra_postargs:
                ld_args.extend(extra_postargs)
            
            self.mkpath(os.path.dirname(output_filename))
            
            if target_desc == CCompiler.EXECUTABLE:
                linker = self.linker_exe[:]
            else:
                linker = self.linker_so[:]
            
            command = linker + ld_args
            
            try:
                self.spawn(command)
            except DistutilsExecError as e:
                msg = str(e)
                raise LinkError(msg) from None
        else:
            log.debug("skipping %s (up-to-date)", output_filename)
    
def _environment_hook(self, name, hook_name):
    if hook_name is None:
        return None
    if is_string(hook_name):
        if hook_name.startswith('self.'):
            hook_name = hook_name[5:]
            hook = getattr(self, hook_name)
            return hook()
        elif hook_name.startswith('exe.'):
            hook_name = hook_name[4:]
            var = self.executables[hook_name]
            if var:
                return var[0]
            else:
                return None
        elif hook_name.startswith('flags.'):
            hook_name = hook_name[6:]
            hook = getattr(self, 'get_flags_' + hook_name)
            return hook()
    else:
        return hook_name()

def can_ccompiler_link(self, ccompiler):
    return True
    
    def wrap_unlinkable_objects(self, objects, output_dir, extra_dll_dir):
        raise NotImplementedError()
    
    ## class FCompiler

_default_compilers = (
    ('win32', ('gnu', 'intelv', 'absoft', 'compaqv', 'intelev', 'gnu95', 'g95',
               'intelvem', 'intelem', 'flang')),
    ('cygwin.*', ('gnu', 'intelv', 'absoft', 'compaqv', 'intelev', 'gnu95', 'g95')),
    ('linux.*', ('arm', 'gnu95', 'intel', 'lahey', 'pg', 'nv', 'absoft', 'nag',
                 'vast', 'compaq', 'intele', 'intelem', 'gnu', 'g95', 
                 'pathf95', 'nagfor', 'fujitsu')),
    ('darwin.*', ('gnu95', 'nag', 'nagfor', 'absoft', 'ibm', 'intel', 'gnu',
                 'g95', 'pg')),
    ('sunos.*', ('sun', 'gnu', 'gnu95', 'g95')),
    ('irix.*', ('mips', 'gnu', 'gnu95',)),
    ('aix.*', ('ibm', 'gnu', 'gnu95',)),
    ('posix', ('gnu', 'gnu95',)),
    ('nt', ('gnu', 'gnu95',)),
    ('mac', ('gnu95', 'gnu', 'pg')),
)

fcompiler_class = None
fcompiler_aliases = None

def load_all_fcompiler_classes():
    from glob import glob
    global fcompiler_class, fcompiler_aliases
    if fcompiler_class is not None:
        return
    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):
    from numpy.distutils.core import get_distribution
    dist = get_distribution(always=True)
    for compiler_type in compiler_types:
        v = None
        try:
            c = new_fcompiler(plat=platform, compiler=compiler_type,
                              c_compiler=c_compiler)
            c.customize(dist)
            v = c.get_version()
            
            if requiref90 and c.compiler_f90 is None:
                v = None
                new_compiler = c.suggested_f90_compiler
                if new_compiler:
                    log.warn('Trying %r compiler as suggested by %r '
                             'compiler for f90 support.' % (compiler_type,
                                                            new_compiler))
                    c = new_fcompiler(plat=platform, compiler=new_compiler,
                                      c_compiler=c_compiler)
                    c.customize(dist)
                    v = c.get_version()
                    if v is not None:
                        compiler_type = new_compiler
                        
            if requiref90 and c.compiler_f90 is None:
                raise ValueError('%s does not support compiling f90 codes, '
                                 'skipping.' % (c.__class__.__name__))
                                 
        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)
            
        if v is not None:
            return compiler_type
    
    return None

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')
    return matching_compiler_types

def get_default_fcompiler(osname=None, platform=None, requiref90=False,
                          c_compiler=None):
    matching_compiler_types = available_fcompilers_for_platform(osname,
                                                                platform)
    log.info("get_default_fcompiler: matching types: '%s'",
             matching_compiler_types)
    compiler_type =  _find_existing_fcompiler(matching_compiler_types,
                                              osname=osname,
                                              platform=platform,
                                              requiref90=requiref90,
                                              c_compiler=c_compiler)
    return compiler_type

failed_fcompilers = set()

def new_fcompiler(plat=None,
                  compiler=None,
                  verbose=0,
                  dry_run=0,
                  force=0,
                  requiref90=False,
                  c_compiler=None):
    global failed_fcompilers
    fcompiler_key = (plat, compiler)
    if fcompiler_key in failed_fcompilers:
        return None

    load_all_fcompiler_classes()
    if plat is None:
        plat = os.name
    if compiler is None:
        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:
        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

def show_fcompilers(dist=None):
    if dist is None:
        from distutils.dist import Distribution
        from numpy.distutils.command.config_compiler import config_fc
        dist = Distribution()
        dist.script_name = os.path.basename(sys.argv[0])
        dist.script_args = ['config_fc'] + sys.argv[1:]
        try:
            dist.script_args.remove('--help-fcompiler')
        except ValueError:
            pass
        dist.cmdclass['config_fc'] = config_fc
        dist.parse_config_files()
        dist.parse_command_line()

    compilers = []
    compilers_na = []
    compilers_ni = []

    if not fcompiler_class:
        load_all_fcompiler_classes()

    platform_compilers = available_fcompilers_for_platform()

    for compiler in platform_compilers:
        v = None
        log.set_verbosity(-2)
        try:
            c = new_fcompiler(compiler=compiler, verbose=dist.verbose)
            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()

    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.")

def dummy_fortran_file():
    fo, name = make_temp_file(suffix='.f')
    fo.write("      subroutine dummy()\n      end\n")
    fo.close()
    return name[:-2]

_has_f_header = re.compile(r'-\*-\s*fortran\s*-\*-', re.I).search
_has_f90_header = re.compile(r'-\*-\s*f90\s*-\*-', re.I).search
_has_fix_header = re.compile(r'-\*-\s*fix\s*-\*-', re.I).search
_free_f90_start = re.compile(r'[^c*!]\s*[^\\d\\t]', re.I).match

def is_free_format(file):
    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
                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):
    with open(src, encoding='latin1') as f:
        line = f.readline()
    return _has_f90_header(line) or _has_fix_header(line)

_f77flags_re = re.compile(r'(c|)f77flags\s*\(\s*(?P<fcname>\w+)\s*\)\s*=\s*(?P<fflags>.*)', re.I)

def get_f77flags(src):
    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()

O arquivo __init__.py é o ponto central para a abstração de compiladores Fortran. Ele define a classe base abstrata FCompiler, que estabelece a interface comum para todos os compiladores Fortran. Funções como new_fcompiler e show_fcompilers são fornecidas para criar instâncias de compiladores e listar os compiladores disponíveis.

# .\numpy\numpy\distutils\fcompiler\fujitsuccompiler.py
from distutils.unixccompiler import UnixCCompiler

class FujitsuCCompiler(UnixCCompiler):
    """
    Fujitsu compiler.
    """

    compiler_type = 'fujitsu'
    
    cc_exe = 'fcc'
    
    cxx_exe = 'FCC'

    def __init__(self, verbose=0, dry_run=0, force=0):
        UnixCCompiler.__init__(self, verbose, dry_run, force)
        
        cc_compiler = self.cc_exe
        
        self.set_executables(
            compiler=cc_compiler +
            ' -O3 -Nclang -fPIC',
            compiler_so=cc_compiler +
            ' -O3 -Nclang -fPIC',
            compiler_cxx=cxx_compiler +
            ' -O3 -Nclang -fPIC',
            linker_exe=cc_compiler +
            ' -lfj90i -lfj90f -lfjsrcinfo -lelf -shared',
            linker_so=cc_compiler +
            ' -lfj90i -lfj90f -lfjsrcinfo -lelf -shared'
        )

O arquivo fujitsuccompiler.py define o compilador FujitsuCCompiler, herdando de UnixCCompiler. Ele configura os comandos e flags para usar o compilador Fortran da Fujitsu.

# .\numpy\numpy\distutils\intelccompiler.py
import platform

from distutils.unixccompiler import UnixCCompiler

from numpy.distutils.exec_command import find_executable

from numpy.distutils.ccompiler import simple_version_match

if platform.system() == 'Windows':
    from numpy.distutils.msvc9compiler import MSVCCompiler


class IntelCCompiler(UnixCCompiler):
    """A modified Intel compiler compatible with a GCC-built Python."""
    compiler_type = 'intel'
    cc_exe = 'icc'
    cc_args = 'fPIC'

    def __init__(self, verbose=0, dry_run=0, force=0):
        UnixCCompiler.__init__(self, verbose, dry_run, force)

        v = self.get_version()
        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):
    compiler_type = 'intele'

    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.
    """
    compiler_type = 'intelem'
    cc_exe = 'icc -m64'
    cc_args = '-fPIC'

    def __init__(self, verbose=0, dry_run=0, force=0):
        UnixCCompiler.__init__(self, verbose, dry_run, force)

        v = self.get_version()
        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')

if platform.system() == 'Windows':
    class IntelCCompilerW(MSVCCompiler):
        """
        A modified Intel compiler compatible with an MSVC-built Python.
        """
        
        compiler_type = 'intelw'
        
        compiler_cxx = 'icl'

        def __init__(self, verbose=0, dry_run=0, force=0):
            MSVCCompiler.__init__(self, verbose, dry_run, force)
            
            version_match = simple_version_match(start=r'Intel\(R\).*?32,')
            self.__version = version_match

        def initialize(self, plat_name=None):
            MSVCCompiler.initialize(self, plat_name)
            
            self.cc = self.find_exe('icl.exe')
            
            self.lib = self.find_exe('xilib')
            
            self.linker = self.find_exe('xilink')
            
            self.compile_options = ['/nologo', '/O3', '/MD', '/W3',
                                    '/Qstd=c99']
            
            self.compile_options_debug = ['/nologo', '/Od', '/MDd', '/W3',
                                          '/Qstd=c99', '/Z7', '/D_DEBUG']

    class IntelEM64TCCompilerW(IntelCCompilerW):
        """
        A modified Intel x86_64 compiler compatible with
        a 64bit MSVC-built Python.
        """
        
        compiler_type = 'intelemw'

        def __init__(self, verbose=0, dry_run=0, force=0):
            MSVCCompiler.__init__(self, verbose, dry_run, force)
            
            version_match = simple_version_match(start=r'Intel\(R\).*?64,')
            self.__version = version_match

O arquivo intelccompiler.py fornece implementações para compilar código com os compiladores Entel (icc e icl), tanto para sistemas baseados em Unix quanto em Windows. Ele inclui classes como IntelCCompiler, IntelItaniumCCompiler e IntelEM64TCCompiler, com suporte específico para diferentes arquiteturas e sistemas operacionais.

# .\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> where anywhere inside a block '</p><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>, a short form of the named, useful when no </s></p><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>
  <ftype complex="" precision="">
  <ftypereal precision="">
  <ctype>
  <ctypereal>

"""

__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:
        name = rep[0].strip() or unique_key(names)
        repl = rep[1].replace(r'\,', '@comma@')
        thelist = conv(repl)
        names[name] = thelist
    return names

def find_and_remove_repl_patterns(astr):
    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]
    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

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@')
    lnames = find_repl_patterns(substr)
    substr = named_re.sub(r"<\1>", substr)

    def listrepl(mobj):
        thelist = conv(mobj.group(1).replace(r'\,', '@comma@'))
        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

    substr = list_re.sub(listrepl, substr)

    numsubs = None
    base_rule = None
    rules = {}

    for r in template_re.findall(substr):
        if r not in rules:
            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))
    
    if not rules:
        return substr

    def namerepl(mobj):
        name = mobj.group(1)
        return rules.get(name, (k+1)*[name])[k]

    newstr = ''
    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 = ''

    struct = parse_structure(newstr)

    oldend = 0
    names = {}
    names.update(_special_names)
    
    for sub in struct:
        cleanedstr, defs = find_and_remove_repl_patterns(newstr[oldend:sub[0]])
        writestr += cleanedstr
        names.update(defs)
        writestr += expand_sub(newstr[sub[0]:sub[1]], names)
        oldend = sub[1]
    
    writestr += newstr[oldend:]

    return writestr

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>
<ftype complex="" precision="">
<ctype>
<ftypereal precision="">
<ctypereal>
''')

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()
</ctypereal></ftypereal></ctype></ftype></prefix></ctypereal></ctype></ftypereal></ftype></prefix></index></index></p>

O arquivo from_template.py é uma ferramenta para processar arquivos de template Fortran. Ele permite a geração de múltiplas versões de código a partir de um único arquivo template, substituindo placeholders definidos entre colchetes (<...>). Isso é útil para criar código que precisa ser compilado com diferentes tipos de dados (real, double precision, complex) ou com diferentes convenções de chamada.

# .\numpy\numpy\distutils\lib2def.py
import re
import sys
import 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])

DEFAULT_NM = ['nm', '-Cs']

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

EXPORTS
""" % py_ver

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')"""
    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

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

def output_def(dlist, flist, header, file = sys.stdout):
    """Outputs the final DEF file to stdout or a file."""
    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()
    if deffile is None:
        deffile = sys.stdout
    else:
        deffile = open(deffile, 'w')
    nm_cmd = DEFAULT_NM + [str(libfile)]
    nm_output = getnm(nm_cmd, shell=False)
    dlist, flist = parse_nm(nm_output)
    output_def(dlist, flist, DEF_HEADER, deffile)

O script lib2def.py é uma ferramenta auxiliar que gera arquivos .def (definition files) a partir de bibliotecas de importação de DLLs compiladas com MSVC. Ele usa o utilitário nm para extrair os símbolos de dados e funções, permitindo a correta definição de exports para DLLs.

O arquivo line_endings.py contém funções para converter fim de linha entre os formatos DOS (CRLF) e Unix (LF) em arquivos. Isso é útil para garantir a consistência nos finais de linha em diferentes sistemas operacionais.

Finalmente, o arquivo fujitsuccompiler.py fornece a implementação para o compilador Fujitsu.

Esses arquivos demonstram como o NumPy gerencia a complexidade de interagir com uma variedade de compiladores Fortran em diferentes plataformas, abstraindo as diferenças e fornecendo uma interface consistente para o processo de construção.

Tags: NumPy distutils fortran compiler Build System

Publicado em 6-25 04:47