Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
unix.py423 linesDownload Raw Back to C
1"""distutils.unixccompiler2 3Contains the UnixCCompiler class, a subclass of CCompiler that handles4the "typical" Unix-style command-line C compiler:5  * macros defined with -Dname[=value]6  * macros undefined with -Uname7  * include search directories specified with -Idir8  * libraries specified with -lllib9  * library search directories specified with -Ldir10  * compile handled by 'cc' (or similar) executable with -c option:11    compiles .c to .o12  * link static library handled by 'ar' command (possibly with 'ranlib')13  * link shared library handled by 'cc -shared'14"""15 16from __future__ import annotations17 18import itertools19import os20import re21import shlex22import sys23from collections.abc import Iterable24 25from ... import sysconfig26from ..._log import log27from ..._macos_compat import compiler_fixup28from ..._modified import newer29from ...compat import consolidate_linker_args30from ...errors import DistutilsExecError31from . import base32from .base import _Macro, gen_lib_options, gen_preprocess_options33from .errors import (34    CompileError,35    LibError,36    LinkError,37)38 39# XXX Things not currently handled:40#   * optimization/debug/warning flags; we just use whatever's in Python's41#     Makefile and live with it.  Is this adequate?  If not, we might42#     have to have a bunch of subclasses GNUCCompiler, SGICCompiler,43#     SunCCompiler, and I suspect down that road lies madness.44#   * even if we don't know a warning flag from an optimization flag,45#     we need some way for outsiders to feed preprocessor/compiler/linker46#     flags in to us -- eg. a sysadmin might want to mandate certain flags47#     via a site config file, or a user might want to set something for48#     compiling this module distribution only via the setup.py command49#     line, whatever.  As long as these options come from something on the50#     current system, they can be as system-dependent as they like, and we51#     should just happily stuff them into the preprocessor/compiler/linker52#     options and carry on.53 54 55def _split_env(cmd):56    """57    For macOS, split command into 'env' portion (if any)58    and the rest of the linker command.59 60    >>> _split_env(['a', 'b', 'c'])61    ([], ['a', 'b', 'c'])62    >>> _split_env(['/usr/bin/env', 'A=3', 'gcc'])63    (['/usr/bin/env', 'A=3'], ['gcc'])64    """65    pivot = 066    if os.path.basename(cmd[0]) == "env":67        pivot = 168        while '=' in cmd[pivot]:69            pivot += 170    return cmd[:pivot], cmd[pivot:]71 72 73def _split_aix(cmd):74    """75    AIX platforms prefix the compiler with the ld_so_aix76    script, so split that from the linker command.77 78    >>> _split_aix(['a', 'b', 'c'])79    ([], ['a', 'b', 'c'])80    >>> _split_aix(['/bin/foo/ld_so_aix', 'gcc'])81    (['/bin/foo/ld_so_aix'], ['gcc'])82    """83    pivot = os.path.basename(cmd[0]) == 'ld_so_aix'84    return cmd[:pivot], cmd[pivot:]85 86 87def _linker_params(linker_cmd, compiler_cmd):88    """89    The linker command usually begins with the compiler90    command (possibly multiple elements), followed by zero or more91    params for shared library building.92 93    If the LDSHARED env variable overrides the linker command,94    however, the commands may not match.95 96    Return the best guess of the linker parameters by stripping97    the linker command. If the compiler command does not98    match the linker command, assume the linker command is99    just the first element.100 101    >>> _linker_params('gcc foo bar'.split(), ['gcc'])102    ['foo', 'bar']103    >>> _linker_params('gcc foo bar'.split(), ['other'])104    ['foo', 'bar']105    >>> _linker_params('ccache gcc foo bar'.split(), 'ccache gcc'.split())106    ['foo', 'bar']107    >>> _linker_params(['gcc'], ['gcc'])108    []109    """110    c_len = len(compiler_cmd)111    pivot = c_len if linker_cmd[:c_len] == compiler_cmd else 1112    return linker_cmd[pivot:]113 114 115class Compiler(base.Compiler):116    compiler_type = 'unix'117 118    # These are used by CCompiler in two places: the constructor sets119    # instance attributes 'preprocessor', 'compiler', etc. from them, and120    # 'set_executable()' allows any of these to be set.  The defaults here121    # are pretty generic; they will probably have to be set by an outsider122    # (eg. using information discovered by the sysconfig about building123    # Python extensions).124    executables = {125        'preprocessor': None,126        'compiler': ["cc"],127        'compiler_so': ["cc"],128        'compiler_cxx': ["c++"],129        'compiler_so_cxx': ["c++"],130        'linker_so': ["cc", "-shared"],131        'linker_so_cxx': ["c++", "-shared"],132        'linker_exe': ["cc"],133        'linker_exe_cxx': ["c++", "-shared"],134        'archiver': ["ar", "-cr"],135        'ranlib': None,136    }137 138    if sys.platform[:6] == "darwin":139        executables['ranlib'] = ["ranlib"]140 141    # Needed for the filename generation methods provided by the base142    # class, CCompiler.  NB. whoever instantiates/uses a particular143    # UnixCCompiler instance should set 'shared_lib_ext' -- we set a144    # reasonable common default here, but it's not necessarily used on all145    # Unices!146 147    src_extensions = [".c", ".C", ".cc", ".cxx", ".cpp", ".m"]148    obj_extension = ".o"149    static_lib_extension = ".a"150    shared_lib_extension = ".so"151    dylib_lib_extension = ".dylib"152    xcode_stub_lib_extension = ".tbd"153    static_lib_format = shared_lib_format = dylib_lib_format = "lib%s%s"154    xcode_stub_lib_format = dylib_lib_format155    if sys.platform == "cygwin":156        exe_extension = ".exe"157        shared_lib_extension = ".dll.a"158        dylib_lib_extension = ".dll"159        dylib_lib_format = "cyg%s%s"160 161    def _fix_lib_args(self, libraries, library_dirs, runtime_library_dirs):162        """Remove standard library path from rpath"""163        libraries, library_dirs, runtime_library_dirs = super()._fix_lib_args(164            libraries, library_dirs, runtime_library_dirs165        )166        libdir = sysconfig.get_config_var('LIBDIR')167        if (168            runtime_library_dirs169            and libdir.startswith("/usr/lib")170            and (libdir in runtime_library_dirs)171        ):172            runtime_library_dirs.remove(libdir)173        return libraries, library_dirs, runtime_library_dirs174 175    def preprocess(176        self,177        source: str | os.PathLike[str],178        output_file: str | os.PathLike[str] | None = None,179        macros: list[_Macro] | None = None,180        include_dirs: list[str] | tuple[str, ...] | None = None,181        extra_preargs: list[str] | None = None,182        extra_postargs: Iterable[str] | None = None,183    ):184        fixed_args = self._fix_compile_args(None, macros, include_dirs)185        ignore, macros, include_dirs = fixed_args186        pp_opts = gen_preprocess_options(macros, include_dirs)187        pp_args = self.preprocessor + pp_opts188        if output_file:189            pp_args.extend(['-o', output_file])190        if extra_preargs:191            pp_args[:0] = extra_preargs192        if extra_postargs:193            pp_args.extend(extra_postargs)194        pp_args.append(source)195 196        # reasons to preprocess:197        # - force is indicated198        # - output is directed to stdout199        # - source file is newer than the target200        preprocess = self.force or output_file is None or newer(source, output_file)201        if not preprocess:202            return203 204        if output_file:205            self.mkpath(os.path.dirname(output_file))206 207        try:208            self.spawn(pp_args)209        except DistutilsExecError as msg:210            raise CompileError(msg)211 212    def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts):213        compiler_so = compiler_fixup(self.compiler_so, cc_args + extra_postargs)214        compiler_so_cxx = compiler_fixup(self.compiler_so_cxx, cc_args + extra_postargs)215        try:216            if self.detect_language(src) == 'c++':217                self.spawn(218                    compiler_so_cxx + cc_args + [src, '-o', obj] + extra_postargs219                )220            else:221                self.spawn(compiler_so + cc_args + [src, '-o', obj] + extra_postargs)222        except DistutilsExecError as msg:223            raise CompileError(msg)224 225    def create_static_lib(226        self, objects, output_libname, output_dir=None, debug=False, target_lang=None227    ):228        objects, output_dir = self._fix_object_args(objects, output_dir)229 230        output_filename = self.library_filename(output_libname, output_dir=output_dir)231 232        if self._need_link(objects, output_filename):233            self.mkpath(os.path.dirname(output_filename))234            self.spawn(self.archiver + [output_filename] + objects + self.objects)235 236            # Not many Unices required ranlib anymore -- SunOS 4.x is, I237            # think the only major Unix that does.  Maybe we need some238            # platform intelligence here to skip ranlib if it's not239            # needed -- or maybe Python's configure script took care of240            # it for us, hence the check for leading colon.241            if self.ranlib:242                try:243                    self.spawn(self.ranlib + [output_filename])244                except DistutilsExecError as msg:245                    raise LibError(msg)246        else:247            log.debug("skipping %s (up-to-date)", output_filename)248 249    def link(250        self,251        target_desc,252        objects: list[str] | tuple[str, ...],253        output_filename,254        output_dir: str | None = None,255        libraries: list[str] | tuple[str, ...] | None = None,256        library_dirs: list[str] | tuple[str, ...] | None = None,257        runtime_library_dirs: list[str] | tuple[str, ...] | None = None,258        export_symbols=None,259        debug=False,260        extra_preargs=None,261        extra_postargs=None,262        build_temp=None,263        target_lang=None,264    ):265        objects, output_dir = self._fix_object_args(objects, output_dir)266        fixed_args = self._fix_lib_args(libraries, library_dirs, runtime_library_dirs)267        libraries, library_dirs, runtime_library_dirs = fixed_args268 269        lib_opts = gen_lib_options(self, library_dirs, runtime_library_dirs, libraries)270        if not isinstance(output_dir, (str, type(None))):271            raise TypeError("'output_dir' must be a string or None")272        if output_dir is not None:273            output_filename = os.path.join(output_dir, output_filename)274 275        if self._need_link(objects, output_filename):276            ld_args = objects + self.objects + lib_opts + ['-o', output_filename]277            if debug:278                ld_args[:0] = ['-g']279            if extra_preargs:280                ld_args[:0] = extra_preargs281            if extra_postargs:282                ld_args.extend(extra_postargs)283            self.mkpath(os.path.dirname(output_filename))284            try:285                # Select a linker based on context: linker_exe when286                # building an executable or linker_so (with shared options)287                # when building a shared library.288                building_exe = target_desc == base.Compiler.EXECUTABLE289                target_cxx = target_lang == "c++"290                linker = (291                    (self.linker_exe_cxx if target_cxx else self.linker_exe)292                    if building_exe293                    else (self.linker_so_cxx if target_cxx else self.linker_so)294                )[:]295 296                if target_cxx and self.compiler_cxx:297                    env, linker_ne = _split_env(linker)298                    aix, linker_na = _split_aix(linker_ne)299                    _, compiler_cxx_ne = _split_env(self.compiler_cxx)300                    _, linker_exe_ne = _split_env(self.linker_exe_cxx)301 302                    params = _linker_params(linker_na, linker_exe_ne)303                    linker = env + aix + compiler_cxx_ne + params304 305                linker = compiler_fixup(linker, ld_args)306 307                self.spawn(linker + ld_args)308            except DistutilsExecError as msg:309                raise LinkError(msg)310        else:311            log.debug("skipping %s (up-to-date)", output_filename)312 313    # -- Miscellaneous methods -----------------------------------------314    # These are all used by the 'gen_lib_options() function, in315    # ccompiler.py.316 317    def library_dir_option(self, dir):318        return "-L" + dir319 320    def _is_gcc(self):321        cc_var = sysconfig.get_config_var("CC")322        compiler = os.path.basename(shlex.split(cc_var)[0])323        return "gcc" in compiler or "g++" in compiler324 325    def runtime_library_dir_option(self, dir: str) -> str | list[str]:  # type: ignore[override] # Fixed in pypa/distutils#339326        # XXX Hackish, at the very least.  See Python bug #445902:327        # https://bugs.python.org/issue445902328        # Linkers on different platforms need different options to329        # specify that directories need to be added to the list of330        # directories searched for dependencies when a dynamic library331        # is sought.  GCC on GNU systems (Linux, FreeBSD, ...) has to332        # be told to pass the -R option through to the linker, whereas333        # other compilers and gcc on other systems just know this.334        # Other compilers may need something slightly different.  At335        # this time, there's no way to determine this information from336        # the configuration data stored in the Python installation, so337        # we use this hack.338        if sys.platform[:6] == "darwin":339            from distutils.util import get_macosx_target_ver, split_version340 341            macosx_target_ver = get_macosx_target_ver()342            if macosx_target_ver and split_version(macosx_target_ver) >= [10, 5]:343                return "-Wl,-rpath," + dir344            else:  # no support for -rpath on earlier macOS versions345                return "-L" + dir346        elif sys.platform[:7] == "freebsd":347            return "-Wl,-rpath=" + dir348        elif sys.platform[:5] == "hp-ux":349            return [350                "-Wl,+s" if self._is_gcc() else "+s",351                "-L" + dir,352            ]353 354        # For all compilers, `-Wl` is the presumed way to pass a355        # compiler option to the linker356        if sysconfig.get_config_var("GNULD") == "yes":357            return consolidate_linker_args([358                # Force RUNPATH instead of RPATH359                "-Wl,--enable-new-dtags",360                "-Wl,-rpath," + dir,361            ])362        else:363            return "-Wl,-R" + dir364 365    def library_option(self, lib):366        return "-l" + lib367 368    @staticmethod369    def _library_root(dir):370        """371        macOS users can specify an alternate SDK using'-isysroot'.372        Calculate the SDK root if it is specified.373 374        Note that, as of Xcode 7, Apple SDKs may contain textual stub375        libraries with .tbd extensions rather than the normal .dylib376        shared libraries installed in /.  The Apple compiler tool377        chain handles this transparently but it can cause problems378        for programs that are being built with an SDK and searching379        for specific libraries.  Callers of find_library_file need to380        keep in mind that the base filename of the returned SDK library381        file might have a different extension from that of the library382        file installed on the running system, for example:383          /Applications/Xcode.app/Contents/Developer/Platforms/384              MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/385              usr/lib/libedit.tbd386        vs387          /usr/lib/libedit.dylib388        """389        cflags = sysconfig.get_config_var('CFLAGS')390        match = re.search(r'-isysroot\s*(\S+)', cflags)391 392        apply_root = (393            sys.platform == 'darwin'394            and match395            and (396                dir.startswith('/System/')397                or (dir.startswith('/usr/') and not dir.startswith('/usr/local/'))398            )399        )400 401        return os.path.join(match.group(1), dir[1:]) if apply_root else dir402 403    def find_library_file(self, dirs, lib, debug=False):404        """405        Second-guess the linker with not much hard406        data to go on: GCC seems to prefer the shared library, so407        assume that *all* Unix C compilers do,408        ignoring even GCC's "-static" option.409        """410        lib_names = (411            self.library_filename(lib, lib_type=type)412            for type in 'dylib xcode_stub shared static'.split()413        )414 415        roots = map(self._library_root, dirs)416 417        searched = itertools.starmap(os.path.join, itertools.product(roots, lib_names))418 419        found = filter(os.path.exists, searched)420 421        # Return None if it could not be found in any dir.422        return next(found, None)423 
codekingpro/portable-devtools · Team Ai