CoolFace
Apppublic

Aluode/PerceptionLabPortable

sourceHugging Faceupdated 9mo agoView on Hugging Face
0likes
build_ext.py471 linesDownload Raw Back to command
1from __future__ import annotations2 3import itertools4import os5import sys6import textwrap7from collections.abc import Iterator8from importlib.machinery import EXTENSION_SUFFIXES9from importlib.util import cache_from_source as _compiled_file_name10from pathlib import Path11from typing import TYPE_CHECKING12 13from setuptools.dist import Distribution14from setuptools.errors import BaseError15from setuptools.extension import Extension, Library16 17from distutils import log18from distutils.ccompiler import new_compiler19from distutils.sysconfig import customize_compiler, get_config_var20 21if TYPE_CHECKING:22    # Cython not installed on CI tests, causing _build_ext to be `Any`23    from distutils.command.build_ext import build_ext as _build_ext24else:25    try:26        # Attempt to use Cython for building extensions, if available27        from Cython.Distutils.build_ext import build_ext as _build_ext28 29        # Additionally, assert that the compiler module will load30        # also. Ref #1229.31        __import__('Cython.Compiler.Main')32    except ImportError:33        from distutils.command.build_ext import build_ext as _build_ext34 35# make sure _config_vars is initialized36get_config_var("LDSHARED")37# Not publicly exposed in typeshed distutils stubs, but this is done on purpose38# See https://github.com/pypa/setuptools/pull/4228#issuecomment-195985640039from distutils.sysconfig import _config_vars as _CONFIG_VARS  # noqa: E40240 41 42def _customize_compiler_for_shlib(compiler):43    if sys.platform == "darwin":44        # building .dylib requires additional compiler flags on OSX; here we45        # temporarily substitute the pyconfig.h variables so that distutils'46        # 'customize_compiler' uses them before we build the shared libraries.47        tmp = _CONFIG_VARS.copy()48        try:49            # XXX Help!  I don't have any idea whether these are right...50            _CONFIG_VARS['LDSHARED'] = (51                "gcc -Wl,-x -dynamiclib -undefined dynamic_lookup"52            )53            _CONFIG_VARS['CCSHARED'] = " -dynamiclib"54            _CONFIG_VARS['SO'] = ".dylib"55            customize_compiler(compiler)56        finally:57            _CONFIG_VARS.clear()58            _CONFIG_VARS.update(tmp)59    else:60        customize_compiler(compiler)61 62 63have_rtld = False64use_stubs = False65libtype = 'shared'66 67if sys.platform == "darwin":68    use_stubs = True69elif os.name != 'nt':70    try:71        import dl  # type: ignore[import-not-found] # https://github.com/python/mypy/issues/1300272 73        use_stubs = have_rtld = hasattr(dl, 'RTLD_NOW')74    except ImportError:75        pass76 77 78def get_abi3_suffix():79    """Return the file extension for an abi3-compliant Extension()"""80    for suffix in EXTENSION_SUFFIXES:81        if '.abi3' in suffix:  # Unix82            return suffix83        elif suffix == '.pyd':  # Windows84            return suffix85    return None86 87 88class build_ext(_build_ext):89    distribution: Distribution  # override distutils.dist.Distribution with setuptools.dist.Distribution90    editable_mode = False91    inplace = False92 93    def run(self):94        """Build extensions in build directory, then copy if --inplace"""95        old_inplace, self.inplace = self.inplace, False96        _build_ext.run(self)97        self.inplace = old_inplace98        if old_inplace:99            self.copy_extensions_to_source()100 101    def _get_inplace_equivalent(self, build_py, ext: Extension) -> tuple[str, str]:102        fullname = self.get_ext_fullname(ext.name)103        filename = self.get_ext_filename(fullname)104        modpath = fullname.split('.')105        package = '.'.join(modpath[:-1])106        package_dir = build_py.get_package_dir(package)107        inplace_file = os.path.join(package_dir, os.path.basename(filename))108        regular_file = os.path.join(self.build_lib, filename)109        return (inplace_file, regular_file)110 111    def copy_extensions_to_source(self) -> None:112        build_py = self.get_finalized_command('build_py')113        for ext in self.extensions:114            inplace_file, regular_file = self._get_inplace_equivalent(build_py, ext)115 116            # Always copy, even if source is older than destination, to ensure117            # that the right extensions for the current Python/platform are118            # used.119            if os.path.exists(regular_file) or not ext.optional:120                self.copy_file(regular_file, inplace_file, level=self.verbose)121 122            if ext._needs_stub:123                inplace_stub = self._get_equivalent_stub(ext, inplace_file)124                self._write_stub_file(inplace_stub, ext, compile=True)125                # Always compile stub and remove the original (leave the cache behind)126                # (this behaviour was observed in previous iterations of the code)127 128    def _get_equivalent_stub(self, ext: Extension, output_file: str) -> str:129        dir_ = os.path.dirname(output_file)130        _, _, name = ext.name.rpartition(".")131        return f"{os.path.join(dir_, name)}.py"132 133    def _get_output_mapping(self) -> Iterator[tuple[str, str]]:134        if not self.inplace:135            return136 137        build_py = self.get_finalized_command('build_py')138        opt = self.get_finalized_command('install_lib').optimize or ""139 140        for ext in self.extensions:141            inplace_file, regular_file = self._get_inplace_equivalent(build_py, ext)142            yield (regular_file, inplace_file)143 144            if ext._needs_stub:145                # This version of `build_ext` always builds artifacts in another dir,146                # when "inplace=True" is given it just copies them back.147                # This is done in the `copy_extensions_to_source` function, which148                # always compile stub files via `_compile_and_remove_stub`.149                # At the end of the process, a `.pyc` stub file is created without the150                # corresponding `.py`.151 152                inplace_stub = self._get_equivalent_stub(ext, inplace_file)153                regular_stub = self._get_equivalent_stub(ext, regular_file)154                inplace_cache = _compiled_file_name(inplace_stub, optimization=opt)155                output_cache = _compiled_file_name(regular_stub, optimization=opt)156                yield (output_cache, inplace_cache)157 158    def get_ext_filename(self, fullname: str) -> str:159        so_ext = os.getenv('SETUPTOOLS_EXT_SUFFIX')160        if so_ext:161            filename = os.path.join(*fullname.split('.')) + so_ext162        else:163            filename = _build_ext.get_ext_filename(self, fullname)164            ext_suffix = get_config_var('EXT_SUFFIX')165            if not isinstance(ext_suffix, str):166                raise OSError(167                    "Configuration variable EXT_SUFFIX not found for this platform "168                    "and environment variable SETUPTOOLS_EXT_SUFFIX is missing"169                )170            so_ext = ext_suffix171 172        if fullname in self.ext_map:173            ext = self.ext_map[fullname]174            abi3_suffix = get_abi3_suffix()175            if ext.py_limited_api and abi3_suffix:  # Use abi3176                filename = filename[: -len(so_ext)] + abi3_suffix177            if isinstance(ext, Library):178                fn, ext = os.path.splitext(filename)179                return self.shlib_compiler.library_filename(fn, libtype)180            elif use_stubs and ext._links_to_dynamic:181                d, fn = os.path.split(filename)182                return os.path.join(d, 'dl-' + fn)183        return filename184 185    def initialize_options(self):186        _build_ext.initialize_options(self)187        self.shlib_compiler = None188        self.shlibs = []189        self.ext_map = {}190        self.editable_mode = False191 192    def finalize_options(self) -> None:193        _build_ext.finalize_options(self)194        self.extensions = self.extensions or []195        self.check_extensions_list(self.extensions)196        self.shlibs = [ext for ext in self.extensions if isinstance(ext, Library)]197        if self.shlibs:198            self.setup_shlib_compiler()199        for ext in self.extensions:200            ext._full_name = self.get_ext_fullname(ext.name)201        for ext in self.extensions:202            fullname = ext._full_name203            self.ext_map[fullname] = ext204 205            # distutils 3.1 will also ask for module names206            # XXX what to do with conflicts?207            self.ext_map[fullname.split('.')[-1]] = ext208 209            ltd = self.shlibs and self.links_to_dynamic(ext) or False210            ns = ltd and use_stubs and not isinstance(ext, Library)211            ext._links_to_dynamic = ltd212            ext._needs_stub = ns213            filename = ext._file_name = self.get_ext_filename(fullname)214            libdir = os.path.dirname(os.path.join(self.build_lib, filename))215            if ltd and libdir not in ext.library_dirs:216                ext.library_dirs.append(libdir)217            if ltd and use_stubs and os.curdir not in ext.runtime_library_dirs:218                ext.runtime_library_dirs.append(os.curdir)219 220        if self.editable_mode:221            self.inplace = True222 223    def setup_shlib_compiler(self):224        compiler = self.shlib_compiler = new_compiler(225            compiler=self.compiler, dry_run=self.dry_run, force=self.force226        )227        _customize_compiler_for_shlib(compiler)228 229        if self.include_dirs is not None:230            compiler.set_include_dirs(self.include_dirs)231        if self.define is not None:232            # 'define' option is a list of (name,value) tuples233            for name, value in self.define:234                compiler.define_macro(name, value)235        if self.undef is not None:236            for macro in self.undef:237                compiler.undefine_macro(macro)238        if self.libraries is not None:239            compiler.set_libraries(self.libraries)240        if self.library_dirs is not None:241            compiler.set_library_dirs(self.library_dirs)242        if self.rpath is not None:243            compiler.set_runtime_library_dirs(self.rpath)244        if self.link_objects is not None:245            compiler.set_link_objects(self.link_objects)246 247        # hack so distutils' build_extension() builds a library instead248        compiler.link_shared_object = link_shared_object.__get__(compiler)  # type: ignore[method-assign]249 250    def get_export_symbols(self, ext):251        if isinstance(ext, Library):252            return ext.export_symbols253        return _build_ext.get_export_symbols(self, ext)254 255    def build_extension(self, ext) -> None:256        ext._convert_pyx_sources_to_lang()257        _compiler = self.compiler258        try:259            if isinstance(ext, Library):260                self.compiler = self.shlib_compiler261            _build_ext.build_extension(self, ext)262            if ext._needs_stub:263                build_lib = self.get_finalized_command('build_py').build_lib264                self.write_stub(build_lib, ext)265        finally:266            self.compiler = _compiler267 268    def links_to_dynamic(self, ext):269        """Return true if 'ext' links to a dynamic lib in the same package"""270        # XXX this should check to ensure the lib is actually being built271        # XXX as dynamic, and not just using a locally-found version or a272        # XXX static-compiled version273        libnames = dict.fromkeys([lib._full_name for lib in self.shlibs])274        pkg = '.'.join(ext._full_name.split('.')[:-1] + [''])275        return any(pkg + libname in libnames for libname in ext.libraries)276 277    def get_source_files(self) -> list[str]:278        return [*_build_ext.get_source_files(self), *self._get_internal_depends()]279 280    def _get_internal_depends(self) -> Iterator[str]:281        """Yield ``ext.depends`` that are contained by the project directory"""282        project_root = Path(self.distribution.src_root or os.curdir).resolve()283        depends = (dep for ext in self.extensions for dep in ext.depends)284 285        def skip(orig_path: str, reason: str) -> None:286            log.info(287                "dependency %s won't be automatically "288                "included in the manifest: the path %s",289                orig_path,290                reason,291            )292 293        for dep in depends:294            path = Path(dep)295 296            if path.is_absolute():297                skip(dep, "must be relative")298                continue299 300            if ".." in path.parts:301                skip(dep, "can't have `..` segments")302                continue303 304            try:305                resolved = (project_root / path).resolve(strict=True)306            except OSError:307                skip(dep, "doesn't exist")308                continue309 310            try:311                resolved.relative_to(project_root)312            except ValueError:313                skip(dep, "must be inside the project root")314                continue315 316            yield path.as_posix()317 318    def get_outputs(self) -> list[str]:319        if self.inplace:320            return list(self.get_output_mapping().keys())321        return sorted(_build_ext.get_outputs(self) + self.__get_stubs_outputs())322 323    def get_output_mapping(self) -> dict[str, str]:324        """See :class:`setuptools.commands.build.SubCommand`"""325        mapping = self._get_output_mapping()326        return dict(sorted(mapping, key=lambda x: x[0]))327 328    def __get_stubs_outputs(self):329        # assemble the base name for each extension that needs a stub330        ns_ext_bases = (331            os.path.join(self.build_lib, *ext._full_name.split('.'))332            for ext in self.extensions333            if ext._needs_stub334        )335        # pair each base with the extension336        pairs = itertools.product(ns_ext_bases, self.__get_output_extensions())337        return list(base + fnext for base, fnext in pairs)338 339    def __get_output_extensions(self):340        yield '.py'341        yield '.pyc'342        if self.get_finalized_command('build_py').optimize:343            yield '.pyo'344 345    def write_stub(self, output_dir, ext, compile=False) -> None:346        stub_file = os.path.join(output_dir, *ext._full_name.split('.')) + '.py'347        self._write_stub_file(stub_file, ext, compile)348 349    def _write_stub_file(self, stub_file: str, ext: Extension, compile=False):350        log.info("writing stub loader for %s to %s", ext._full_name, stub_file)351        if compile and os.path.exists(stub_file):352            raise BaseError(stub_file + " already exists! Please delete.")353        if not self.dry_run:354            with open(stub_file, 'w', encoding="utf-8") as f:355                content = (356                    textwrap.dedent(f"""357                    def __bootstrap__():358                       global __bootstrap__, __file__, __loader__359                       import sys, os, importlib.resources as irs, importlib.util360                    #rtld   import dl361                       with irs.files(__name__).joinpath(362                         {os.path.basename(ext._file_name)!r}) as __file__:363                          del __bootstrap__364                          if '__loader__' in globals():365                              del __loader__366                    #rtld      old_flags = sys.getdlopenflags()367                          old_dir = os.getcwd()368                          try:369                            os.chdir(os.path.dirname(__file__))370                    #rtld        sys.setdlopenflags(dl.RTLD_NOW)371                            spec = importlib.util.spec_from_file_location(372                                       __name__, __file__)373                            mod = importlib.util.module_from_spec(spec)374                            spec.loader.exec_module(mod)375                          finally:376                    #rtld        sys.setdlopenflags(old_flags)377                            os.chdir(old_dir)378                    __bootstrap__()379                    """)380                    .lstrip()381                    .replace('#rtld', '#rtld' * (not have_rtld))382                )383                f.write(content)384        if compile:385            self._compile_and_remove_stub(stub_file)386 387    def _compile_and_remove_stub(self, stub_file: str):388        from distutils.util import byte_compile389 390        byte_compile([stub_file], optimize=0, force=True, dry_run=self.dry_run)391        optimize = self.get_finalized_command('install_lib').optimize392        if optimize > 0:393            byte_compile(394                [stub_file],395                optimize=optimize,396                force=True,397                dry_run=self.dry_run,398            )399        if os.path.exists(stub_file) and not self.dry_run:400            os.unlink(stub_file)401 402 403if use_stubs or os.name == 'nt':404    # Build shared libraries405    #406    def link_shared_object(407        self,408        objects,409        output_libname,410        output_dir=None,411        libraries=None,412        library_dirs=None,413        runtime_library_dirs=None,414        export_symbols=None,415        debug: bool = False,416        extra_preargs=None,417        extra_postargs=None,418        build_temp=None,419        target_lang=None,420    ) -> None:421        self.link(422            self.SHARED_LIBRARY,423            objects,424            output_libname,425            output_dir,426            libraries,427            library_dirs,428            runtime_library_dirs,429            export_symbols,430            debug,431            extra_preargs,432            extra_postargs,433            build_temp,434            target_lang,435        )436 437else:438    # Build static libraries everywhere else439    libtype = 'static'440 441    def link_shared_object(442        self,443        objects,444        output_libname,445        output_dir=None,446        libraries=None,447        library_dirs=None,448        runtime_library_dirs=None,449        export_symbols=None,450        debug: bool = False,451        extra_preargs=None,452        extra_postargs=None,453        build_temp=None,454        target_lang=None,455    ) -> None:456        # XXX we need to either disallow these attrs on Library instances,457        # or warn/abort here if set, or something...458        # libraries=None, library_dirs=None, runtime_library_dirs=None,459        # export_symbols=None, extra_preargs=None, extra_postargs=None,460        # build_temp=None461 462        assert output_dir is None  # distutils build_ext doesn't pass this463        output_dir, filename = os.path.split(output_libname)464        basename, _ext = os.path.splitext(filename)465        if self.library_filename("x").startswith('lib'):466            # strip 'lib' prefix; this is kludgy if some platform uses467            # a different prefix468            basename = basename[3:]469 470        self.create_static_lib(objects, basename, output_dir, debug, target_lang)471 
Aluode/PerceptionLabPortable · CoolFace