Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
build_ext.py812 linesDownload Raw Back to command
1"""distutils.command.build_ext2 3Implements the Distutils 'build_ext' command, for building extension4modules (currently limited to C extensions, should accommodate C++5extensions ASAP)."""6 7from __future__ import annotations8 9import contextlib10import os11import re12import sys13from collections.abc import Callable14from distutils._log import log15from site import USER_BASE16from typing import ClassVar17 18from .._modified import newer_group19from ..ccompiler import new_compiler, show_compilers20from ..core import Command21from ..errors import (22    CCompilerError,23    CompileError,24    DistutilsError,25    DistutilsOptionError,26    DistutilsPlatformError,27    DistutilsSetupError,28)29from ..extension import Extension30from ..sysconfig import customize_compiler, get_config_h_filename, get_python_version31from ..util import get_platform, is_freethreaded, is_mingw32 33# An extension name is just a dot-separated list of Python NAMEs (ie.34# the same as a fully-qualified module name).35extension_name_re = re.compile(r'^[a-zA-Z_][a-zA-Z_0-9]*(\.[a-zA-Z_][a-zA-Z_0-9]*)*$')36 37 38class build_ext(Command):39    description = "build C/C++ extensions (compile/link to build directory)"40 41    # XXX thoughts on how to deal with complex command-line options like42    # these, i.e. how to make it so fancy_getopt can suck them off the43    # command line and make it look like setup.py defined the appropriate44    # lists of tuples of what-have-you.45    #   - each command needs a callback to process its command-line options46    #   - Command.__init__() needs access to its share of the whole47    #     command line (must ultimately come from48    #     Distribution.parse_command_line())49    #   - it then calls the current command class' option-parsing50    #     callback to deal with weird options like -D, which have to51    #     parse the option text and churn out some custom data52    #     structure53    #   - that data structure (in this case, a list of 2-tuples)54    #     will then be present in the command object by the time55    #     we get to finalize_options() (i.e. the constructor56    #     takes care of both command-line and client options57    #     in between initialize_options() and finalize_options())58 59    sep_by = f" (separated by '{os.pathsep}')"60    user_options = [61        ('build-lib=', 'b', "directory for compiled extension modules"),62        ('build-temp=', 't', "directory for temporary files (build by-products)"),63        (64            'plat-name=',65            'p',66            "platform name to cross-compile for, if supported "67            f"[default: {get_platform()}]",68        ),69        (70            'inplace',71            'i',72            "ignore build-lib and put compiled extensions into the source "73            "directory alongside your pure Python modules",74        ),75        (76            'include-dirs=',77            'I',78            "list of directories to search for header files" + sep_by,79        ),80        ('define=', 'D', "C preprocessor macros to define"),81        ('undef=', 'U', "C preprocessor macros to undefine"),82        ('libraries=', 'l', "external C libraries to link with"),83        (84            'library-dirs=',85            'L',86            "directories to search for external C libraries" + sep_by,87        ),88        ('rpath=', 'R', "directories to search for shared C libraries at runtime"),89        ('link-objects=', 'O', "extra explicit link objects to include in the link"),90        ('debug', 'g', "compile/link with debugging information"),91        ('force', 'f', "forcibly build everything (ignore file timestamps)"),92        ('compiler=', 'c', "specify the compiler type"),93        ('parallel=', 'j', "number of parallel build jobs"),94        ('swig-cpp', None, "make SWIG create C++ files (default is C)"),95        ('swig-opts=', None, "list of SWIG command line options"),96        ('swig=', None, "path to the SWIG executable"),97        ('user', None, "add user include, library and rpath"),98    ]99 100    boolean_options: ClassVar[list[str]] = [101        'inplace',102        'debug',103        'force',104        'swig-cpp',105        'user',106    ]107 108    help_options: ClassVar[list[tuple[str, str | None, str, Callable[[], object]]]] = [109        ('help-compiler', None, "list available compilers", show_compilers),110    ]111 112    def initialize_options(self):113        self.extensions = None114        self.build_lib = None115        self.plat_name = None116        self.build_temp = None117        self.inplace = False118        self.package = None119 120        self.include_dirs = None121        self.define = None122        self.undef = None123        self.libraries = None124        self.library_dirs = None125        self.rpath = None126        self.link_objects = None127        self.debug = None128        self.force = None129        self.compiler = None130        self.swig = None131        self.swig_cpp = None132        self.swig_opts = None133        self.user = None134        self.parallel = None135 136    @staticmethod137    def _python_lib_dir(sysconfig):138        """139        Resolve Python's library directory for building extensions140        that rely on a shared Python library.141 142        See python/cpython#44264 and python/cpython#48686143        """144        if not sysconfig.get_config_var('Py_ENABLE_SHARED'):145            return146 147        if sysconfig.python_build:148            yield '.'149            return150 151        if sys.platform == 'zos':152            # On z/OS, a user is not required to install Python to153            # a predetermined path, but can use Python portably154            installed_dir = sysconfig.get_config_var('base')155            lib_dir = sysconfig.get_config_var('platlibdir')156            yield os.path.join(installed_dir, lib_dir)157        else:158            # building third party extensions159            yield sysconfig.get_config_var('LIBDIR')160 161    def finalize_options(self) -> None:  # noqa: C901162        from distutils import sysconfig163 164        self.set_undefined_options(165            'build',166            ('build_lib', 'build_lib'),167            ('build_temp', 'build_temp'),168            ('compiler', 'compiler'),169            ('debug', 'debug'),170            ('force', 'force'),171            ('parallel', 'parallel'),172            ('plat_name', 'plat_name'),173        )174 175        if self.package is None:176            self.package = self.distribution.ext_package177 178        self.extensions = self.distribution.ext_modules179 180        # Make sure Python's include directories (for Python.h, pyconfig.h,181        # etc.) are in the include search path.182        py_include = sysconfig.get_python_inc()183        plat_py_include = sysconfig.get_python_inc(plat_specific=True)184        if self.include_dirs is None:185            self.include_dirs = self.distribution.include_dirs or []186        if isinstance(self.include_dirs, str):187            self.include_dirs = self.include_dirs.split(os.pathsep)188 189        # If in a virtualenv, add its include directory190        # Issue 16116191        if sys.exec_prefix != sys.base_exec_prefix:192            self.include_dirs.append(os.path.join(sys.exec_prefix, 'include'))193 194        # Put the Python "system" include dir at the end, so that195        # any local include dirs take precedence.196        self.include_dirs.extend(py_include.split(os.path.pathsep))197        if plat_py_include != py_include:198            self.include_dirs.extend(plat_py_include.split(os.path.pathsep))199 200        self.ensure_string_list('libraries')201        self.ensure_string_list('link_objects')202 203        # Life is easier if we're not forever checking for None, so204        # simplify these options to empty lists if unset205        if self.libraries is None:206            self.libraries = []207        if self.library_dirs is None:208            self.library_dirs = []209        elif isinstance(self.library_dirs, str):210            self.library_dirs = self.library_dirs.split(os.pathsep)211 212        if self.rpath is None:213            self.rpath = []214        elif isinstance(self.rpath, str):215            self.rpath = self.rpath.split(os.pathsep)216 217        # for extensions under windows use different directories218        # for Release and Debug builds.219        # also Python's library directory must be appended to library_dirs220        if os.name == 'nt' and not is_mingw():221            # the 'libs' directory is for binary installs - we assume that222            # must be the *native* platform.  But we don't really support223            # cross-compiling via a binary install anyway, so we let it go.224            self.library_dirs.append(os.path.join(sys.exec_prefix, 'libs'))225            if sys.base_exec_prefix != sys.prefix:  # Issue 16116226                self.library_dirs.append(os.path.join(sys.base_exec_prefix, 'libs'))227            if self.debug:228                self.build_temp = os.path.join(self.build_temp, "Debug")229            else:230                self.build_temp = os.path.join(self.build_temp, "Release")231 232            # Append the source distribution include and library directories,233            # this allows distutils on windows to work in the source tree234            self.include_dirs.append(os.path.dirname(get_config_h_filename()))235            self.library_dirs.append(sys.base_exec_prefix)236 237            # Use the .lib files for the correct architecture238            if self.plat_name == 'win32':239                suffix = 'win32'240            else:241                # win-amd64242                suffix = self.plat_name[4:]243            new_lib = os.path.join(sys.exec_prefix, 'PCbuild')244            if suffix:245                new_lib = os.path.join(new_lib, suffix)246            self.library_dirs.append(new_lib)247 248        # For extensions under Cygwin, Python's library directory must be249        # appended to library_dirs250        if sys.platform[:6] == 'cygwin':251            if not sysconfig.python_build:252                # building third party extensions253                self.library_dirs.append(254                    os.path.join(255                        sys.prefix, "lib", "python" + get_python_version(), "config"256                    )257                )258            else:259                # building python standard extensions260                self.library_dirs.append('.')261 262        self.library_dirs.extend(self._python_lib_dir(sysconfig))263 264        # The argument parsing will result in self.define being a string, but265        # it has to be a list of 2-tuples.  All the preprocessor symbols266        # specified by the 'define' option will be set to '1'.  Multiple267        # symbols can be separated with commas.268 269        if self.define:270            defines = self.define.split(',')271            self.define = [(symbol, '1') for symbol in defines]272 273        # The option for macros to undefine is also a string from the274        # option parsing, but has to be a list.  Multiple symbols can also275        # be separated with commas here.276        if self.undef:277            self.undef = self.undef.split(',')278 279        if self.swig_opts is None:280            self.swig_opts = []281        else:282            self.swig_opts = self.swig_opts.split(' ')283 284        # Finally add the user include and library directories if requested285        if self.user:286            user_include = os.path.join(USER_BASE, "include")287            user_lib = os.path.join(USER_BASE, "lib")288            if os.path.isdir(user_include):289                self.include_dirs.append(user_include)290            if os.path.isdir(user_lib):291                self.library_dirs.append(user_lib)292                self.rpath.append(user_lib)293 294        if isinstance(self.parallel, str):295            try:296                self.parallel = int(self.parallel)297            except ValueError:298                raise DistutilsOptionError("parallel should be an integer")299 300    def run(self) -> None:  # noqa: C901301        # 'self.extensions', as supplied by setup.py, is a list of302        # Extension instances.  See the documentation for Extension (in303        # distutils.extension) for details.304        #305        # For backwards compatibility with Distutils 0.8.2 and earlier, we306        # also allow the 'extensions' list to be a list of tuples:307        #    (ext_name, build_info)308        # where build_info is a dictionary containing everything that309        # Extension instances do except the name, with a few things being310        # differently named.  We convert these 2-tuples to Extension311        # instances as needed.312 313        if not self.extensions:314            return315 316        # If we were asked to build any C/C++ libraries, make sure that the317        # directory where we put them is in the library search path for318        # linking extensions.319        if self.distribution.has_c_libraries():320            build_clib = self.get_finalized_command('build_clib')321            self.libraries.extend(build_clib.get_library_names() or [])322            self.library_dirs.append(build_clib.build_clib)323 324        # Setup the CCompiler object that we'll use to do all the325        # compiling and linking326        self.compiler = new_compiler(327            compiler=self.compiler,328            verbose=self.verbose,329            force=self.force,330        )331        customize_compiler(self.compiler)332        # If we are cross-compiling, init the compiler now (if we are not333        # cross-compiling, init would not hurt, but people may rely on334        # late initialization of compiler even if they shouldn't...)335        if os.name == 'nt' and self.plat_name != get_platform():336            self.compiler.initialize(self.plat_name)337 338        # The official Windows free threaded Python installer doesn't set339        # Py_GIL_DISABLED because its pyconfig.h is shared with the340        # default build, so define it here (pypa/setuptools#4662).341        if os.name == 'nt' and is_freethreaded():342            self.compiler.define_macro('Py_GIL_DISABLED', '1')343 344        # And make sure that any compile/link-related options (which might345        # come from the command-line or from the setup script) are set in346        # that CCompiler object -- that way, they automatically apply to347        # all compiling and linking done here.348        if self.include_dirs is not None:349            self.compiler.set_include_dirs(self.include_dirs)350        if self.define is not None:351            # 'define' option is a list of (name,value) tuples352            for name, value in self.define:353                self.compiler.define_macro(name, value)354        if self.undef is not None:355            for macro in self.undef:356                self.compiler.undefine_macro(macro)357        if self.libraries is not None:358            self.compiler.set_libraries(self.libraries)359        if self.library_dirs is not None:360            self.compiler.set_library_dirs(self.library_dirs)361        if self.rpath is not None:362            self.compiler.set_runtime_library_dirs(self.rpath)363        if self.link_objects is not None:364            self.compiler.set_link_objects(self.link_objects)365 366        # Now actually compile and link everything.367        self.build_extensions()368 369    def check_extensions_list(self, extensions) -> None:  # noqa: C901370        """Ensure that the list of extensions (presumably provided as a371        command option 'extensions') is valid, i.e. it is a list of372        Extension objects.  We also support the old-style list of 2-tuples,373        where the tuples are (ext_name, build_info), which are converted to374        Extension instances here.375 376        Raise DistutilsSetupError if the structure is invalid anywhere;377        just returns otherwise.378        """379        if not isinstance(extensions, list):380            raise DistutilsSetupError(381                "'ext_modules' option must be a list of Extension instances"382            )383 384        for i, ext in enumerate(extensions):385            if isinstance(ext, Extension):386                continue  # OK! (assume type-checking done387                # by Extension constructor)388 389            if not isinstance(ext, tuple) or len(ext) != 2:390                raise DistutilsSetupError(391                    "each element of 'ext_modules' option must be an "392                    "Extension instance or 2-tuple"393                )394 395            ext_name, build_info = ext396 397            log.warning(398                "old-style (ext_name, build_info) tuple found in "399                "ext_modules for extension '%s' "400                "-- please convert to Extension instance",401                ext_name,402            )403 404            if not (isinstance(ext_name, str) and extension_name_re.match(ext_name)):405                raise DistutilsSetupError(406                    "first element of each tuple in 'ext_modules' "407                    "must be the extension name (a string)"408                )409 410            if not isinstance(build_info, dict):411                raise DistutilsSetupError(412                    "second element of each tuple in 'ext_modules' "413                    "must be a dictionary (build info)"414                )415 416            # OK, the (ext_name, build_info) dict is type-safe: convert it417            # to an Extension instance.418            ext = Extension(ext_name, build_info['sources'])419 420            # Easy stuff: one-to-one mapping from dict elements to421            # instance attributes.422            for key in (423                'include_dirs',424                'library_dirs',425                'libraries',426                'extra_objects',427                'extra_compile_args',428                'extra_link_args',429            ):430                val = build_info.get(key)431                if val is not None:432                    setattr(ext, key, val)433 434            # Medium-easy stuff: same syntax/semantics, different names.435            ext.runtime_library_dirs = build_info.get('rpath')436            if 'def_file' in build_info:437                log.warning("'def_file' element of build info dict no longer supported")438 439            # Non-trivial stuff: 'macros' split into 'define_macros'440            # and 'undef_macros'.441            macros = build_info.get('macros')442            if macros:443                ext.define_macros = []444                ext.undef_macros = []445                for macro in macros:446                    if not (isinstance(macro, tuple) and len(macro) in (1, 2)):447                        raise DistutilsSetupError(448                            "'macros' element of build info dict must be 1- or 2-tuple"449                        )450                    if len(macro) == 1:451                        ext.undef_macros.append(macro[0])452                    elif len(macro) == 2:453                        ext.define_macros.append(macro)454 455            extensions[i] = ext456 457    def get_source_files(self):458        self.check_extensions_list(self.extensions)459        filenames = []460 461        # Wouldn't it be neat if we knew the names of header files too...462        for ext in self.extensions:463            filenames.extend(ext.sources)464        return filenames465 466    def get_outputs(self):467        # Sanity check the 'extensions' list -- can't assume this is being468        # done in the same run as a 'build_extensions()' call (in fact, we469        # can probably assume that it *isn't*!).470        self.check_extensions_list(self.extensions)471 472        # And build the list of output (built) filenames.  Note that this473        # ignores the 'inplace' flag, and assumes everything goes in the474        # "build" tree.475        return [self.get_ext_fullpath(ext.name) for ext in self.extensions]476 477    def build_extensions(self) -> None:478        # First, sanity-check the 'extensions' list479        self.check_extensions_list(self.extensions)480        if self.parallel:481            self._build_extensions_parallel()482        else:483            self._build_extensions_serial()484 485    def _build_extensions_parallel(self):486        workers = self.parallel487        if self.parallel is True:488            workers = os.cpu_count()  # may return None489        try:490            from concurrent.futures import ThreadPoolExecutor491        except ImportError:492            workers = None493 494        if workers is None:495            self._build_extensions_serial()496            return497 498        with ThreadPoolExecutor(max_workers=workers) as executor:499            futures = [500                executor.submit(self.build_extension, ext) for ext in self.extensions501            ]502            for ext, fut in zip(self.extensions, futures):503                with self._filter_build_errors(ext):504                    fut.result()505 506    def _build_extensions_serial(self):507        for ext in self.extensions:508            with self._filter_build_errors(ext):509                self.build_extension(ext)510 511    @contextlib.contextmanager512    def _filter_build_errors(self, ext):513        try:514            yield515        except (CCompilerError, DistutilsError, CompileError) as e:516            if not ext.optional:517                raise518            self.warn(f'building extension "{ext.name}" failed: {e}')519 520    def build_extension(self, ext) -> None:521        sources = ext.sources522        if sources is None or not isinstance(sources, (list, tuple)):523            raise DistutilsSetupError(524                f"in 'ext_modules' option (extension '{ext.name}'), "525                "'sources' must be present and must be "526                "a list of source filenames"527            )528        # sort to make the resulting .so file build reproducible529        sources = sorted(sources)530 531        ext_path = self.get_ext_fullpath(ext.name)532        depends = sources + ext.depends533        if not (self.force or newer_group(depends, ext_path, 'newer')):534            log.debug("skipping '%s' extension (up-to-date)", ext.name)535            return536        else:537            log.info("building '%s' extension", ext.name)538 539        # First, scan the sources for SWIG definition files (.i), run540        # SWIG on 'em to create .c files, and modify the sources list541        # accordingly.542        sources = self.swig_sources(sources, ext)543 544        # Next, compile the source code to object files.545 546        # XXX not honouring 'define_macros' or 'undef_macros' -- the547        # CCompiler API needs to change to accommodate this, and I548        # want to do one thing at a time!549 550        # Two possible sources for extra compiler arguments:551        #   - 'extra_compile_args' in Extension object552        #   - CFLAGS environment variable (not particularly553        #     elegant, but people seem to expect it and I554        #     guess it's useful)555        # The environment variable should take precedence, and556        # any sensible compiler will give precedence to later557        # command line args.  Hence we combine them in order:558        extra_args = ext.extra_compile_args or []559 560        macros = ext.define_macros[:]561        for undef in ext.undef_macros:562            macros.append((undef,))563 564        objects = self.compiler.compile(565            sources,566            output_dir=self.build_temp,567            macros=macros,568            include_dirs=ext.include_dirs,569            debug=self.debug,570            extra_postargs=extra_args,571            depends=ext.depends,572        )573 574        # XXX outdated variable, kept here in case third-part code575        # needs it.576        self._built_objects = objects[:]577 578        # Now link the object files together into a "shared object" --579        # of course, first we have to figure out all the other things580        # that go into the mix.581        if ext.extra_objects:582            objects.extend(ext.extra_objects)583        extra_args = ext.extra_link_args or []584 585        # Detect target language, if not provided586        language = ext.language or self.compiler.detect_language(sources)587 588        self.compiler.link_shared_object(589            objects,590            ext_path,591            libraries=self.get_libraries(ext),592            library_dirs=ext.library_dirs,593            runtime_library_dirs=ext.runtime_library_dirs,594            extra_postargs=extra_args,595            export_symbols=self.get_export_symbols(ext),596            debug=self.debug,597            build_temp=self.build_temp,598            target_lang=language,599        )600 601    def swig_sources(self, sources, extension):602        """Walk the list of source files in 'sources', looking for SWIG603        interface (.i) files.  Run SWIG on all that are found, and604        return a modified 'sources' list with SWIG source files replaced605        by the generated C (or C++) files.606        """607        new_sources = []608        swig_sources = []609        swig_targets = {}610 611        # XXX this drops generated C/C++ files into the source tree, which612        # is fine for developers who want to distribute the generated613        # source -- but there should be an option to put SWIG output in614        # the temp dir.615 616        if self.swig_cpp:617            log.warning("--swig-cpp is deprecated - use --swig-opts=-c++")618 619        if (620            self.swig_cpp621            or ('-c++' in self.swig_opts)622            or ('-c++' in extension.swig_opts)623        ):624            target_ext = '.cpp'625        else:626            target_ext = '.c'627 628        for source in sources:629            (base, ext) = os.path.splitext(source)630            if ext == ".i":  # SWIG interface file631                new_sources.append(base + '_wrap' + target_ext)632                swig_sources.append(source)633                swig_targets[source] = new_sources[-1]634            else:635                new_sources.append(source)636 637        if not swig_sources:638            return new_sources639 640        swig = self.swig or self.find_swig()641        swig_cmd = [swig, "-python"]642        swig_cmd.extend(self.swig_opts)643        if self.swig_cpp:644            swig_cmd.append("-c++")645 646        # Do not override commandline arguments647        if not self.swig_opts:648            swig_cmd.extend(extension.swig_opts)649 650        for source in swig_sources:651            target = swig_targets[source]652            log.info("swigging %s to %s", source, target)653            self.spawn(swig_cmd + ["-o", target, source])654 655        return new_sources656 657    def find_swig(self):658        """Return the name of the SWIG executable.  On Unix, this is659        just "swig" -- it should be in the PATH.  Tries a bit harder on660        Windows.661        """662        if os.name == "posix":663            return "swig"664        elif os.name == "nt":665            # Look for SWIG in its standard installation directory on666            # Windows (or so I presume!).  If we find it there, great;667            # if not, act like Unix and assume it's in the PATH.668            for vers in ("1.3", "1.2", "1.1"):669                fn = os.path.join(f"c:\\swig{vers}", "swig.exe")670                if os.path.isfile(fn):671                    return fn672            else:673                return "swig.exe"674        else:675            raise DistutilsPlatformError(676                f"I don't know how to find (much less run) SWIG on platform '{os.name}'"677            )678 679    # -- Name generators -----------------------------------------------680    # (extension names, filenames, whatever)681    def get_ext_fullpath(self, ext_name: str) -> str:682        """Returns the path of the filename for a given extension.683 684        The file is located in `build_lib` or directly in the package685        (inplace option).686        """687        fullname = self.get_ext_fullname(ext_name)688        modpath = fullname.split('.')689        filename = self.get_ext_filename(modpath[-1])690 691        if not self.inplace:692            # no further work needed693            # returning :694            #   build_dir/package/path/filename695            filename = os.path.join(*modpath[:-1] + [filename])696            return os.path.join(self.build_lib, filename)697 698        # the inplace option requires to find the package directory699        # using the build_py command for that700        package = '.'.join(modpath[0:-1])701        build_py = self.get_finalized_command('build_py')702        package_dir = os.path.abspath(build_py.get_package_dir(package))703 704        # returning705        #   package_dir/filename706        return os.path.join(package_dir, filename)707 708    def get_ext_fullname(self, ext_name: str) -> str:709        """Returns the fullname of a given extension name.710 711        Adds the `package.` prefix"""712        if self.package is None:713            return ext_name714        else:715            return self.package + '.' + ext_name716 717    def get_ext_filename(self, ext_name: str) -> str:718        r"""Convert the name of an extension (eg. "foo.bar") into the name719        of the file from which it will be loaded (eg. "foo/bar.so", or720        "foo\bar.pyd").721        """722        from ..sysconfig import get_config_var723 724        ext_path = ext_name.split('.')725        ext_suffix = get_config_var('EXT_SUFFIX')726        return os.path.join(*ext_path) + ext_suffix727 728    def get_export_symbols(self, ext: Extension) -> list[str]:729        """Return the list of symbols that a shared extension has to730        export.  This either uses 'ext.export_symbols' or, if it's not731        provided, "PyInit_" + module_name.  Only relevant on Windows, where732        the .pyd file (DLL) must export the module "PyInit_" function.733        """734        name = self._get_module_name_for_symbol(ext)735        try:736            # Unicode module name support as defined in PEP-489737            # https://peps.python.org/pep-0489/#export-hook-name738            name.encode('ascii')739        except UnicodeEncodeError:740            suffix = 'U_' + name.encode('punycode').replace(b'-', b'_').decode('ascii')741        else:742            suffix = "_" + name743 744        initfunc_name = "PyInit" + suffix745        if initfunc_name not in ext.export_symbols:746            ext.export_symbols.append(initfunc_name)747        return ext.export_symbols748 749    def _get_module_name_for_symbol(self, ext):750        # Package name should be used for `__init__` modules751        # https://github.com/python/cpython/issues/80074752        # https://github.com/pypa/setuptools/issues/4826753        parts = ext.name.split(".")754        if parts[-1] == "__init__" and len(parts) >= 2:755            return parts[-2]756        return parts[-1]757 758    def get_libraries(self, ext: Extension) -> list[str]:  # noqa: C901759        """Return the list of libraries to link against when building a760        shared extension.  On most platforms, this is just 'ext.libraries';761        on Windows, we add the Python library (eg. python20.dll).762        """763        # The python library is always needed on Windows.  For MSVC, this764        # is redundant, since the library is mentioned in a pragma in765        # pyconfig.h that MSVC groks.  The other Windows compilers all seem766        # to need it mentioned explicitly, though, so that's what we do.767        # Append '_d' to the python import library on debug builds.768        if sys.platform == "win32" and not is_mingw():769            from .._msvccompiler import MSVCCompiler770 771            if not isinstance(self.compiler, MSVCCompiler):772                template = "python%d%d"773                if self.debug:774                    template = template + '_d'775                pythonlib = template % (776                    sys.hexversion >> 24,777                    (sys.hexversion >> 16) & 0xFF,778                )779                # don't extend ext.libraries, it may be shared with other780                # extensions, it is a reference to the original list781                return ext.libraries + [pythonlib]782        else:783            # On Android only the main executable and LD_PRELOADs are considered784            # to be RTLD_GLOBAL, all the dependencies of the main executable785            # remain RTLD_LOCAL and so the shared libraries must be linked with786            # libpython when python is built with a shared python library (issue787            # bpo-21536).788            # On Cygwin (and if required, other POSIX-like platforms based on789            # Windows like MinGW) it is simply necessary that all symbols in790            # shared libraries are resolved at link time.791            from ..sysconfig import get_config_var792 793            link_libpython = False794            if get_config_var('Py_ENABLE_SHARED'):795                # A native build on an Android device or on Cygwin796                if hasattr(sys, 'getandroidapilevel'):797                    link_libpython = True798                elif sys.platform == 'cygwin' or is_mingw():799                    link_libpython = True800                elif '_PYTHON_HOST_PLATFORM' in os.environ:801                    # We are cross-compiling for one of the relevant platforms802                    if get_config_var('ANDROID_API_LEVEL') != 0:803                        link_libpython = True804                    elif get_config_var('MACHDEP') == 'cygwin':805                        link_libpython = True806 807            if link_libpython:808                ldversion = get_config_var('LDVERSION')809                return ext.libraries + ['python' + ldversion]810 811        return ext.libraries812 
codekingpro/portable-devtools · Team Ai