Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
unixccompiler.py401 linesDownload Raw Back to _distutils
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 sys23 24from . import sysconfig25from .compat import consolidate_linker_args26from ._log import log27from ._macos_compat import compiler_fixup28from ._modified import newer29from .ccompiler import CCompiler, gen_lib_options, gen_preprocess_options30from .errors import CompileError, DistutilsExecError, LibError, LinkError31 32# XXX Things not currently handled:33#   * optimization/debug/warning flags; we just use whatever's in Python's34#     Makefile and live with it.  Is this adequate?  If not, we might35#     have to have a bunch of subclasses GNUCCompiler, SGICCompiler,36#     SunCCompiler, and I suspect down that road lies madness.37#   * even if we don't know a warning flag from an optimization flag,38#     we need some way for outsiders to feed preprocessor/compiler/linker39#     flags in to us -- eg. a sysadmin might want to mandate certain flags40#     via a site config file, or a user might want to set something for41#     compiling this module distribution only via the setup.py command42#     line, whatever.  As long as these options come from something on the43#     current system, they can be as system-dependent as they like, and we44#     should just happily stuff them into the preprocessor/compiler/linker45#     options and carry on.46 47 48def _split_env(cmd):49    """50    For macOS, split command into 'env' portion (if any)51    and the rest of the linker command.52 53    >>> _split_env(['a', 'b', 'c'])54    ([], ['a', 'b', 'c'])55    >>> _split_env(['/usr/bin/env', 'A=3', 'gcc'])56    (['/usr/bin/env', 'A=3'], ['gcc'])57    """58    pivot = 059    if os.path.basename(cmd[0]) == "env":60        pivot = 161        while '=' in cmd[pivot]:62            pivot += 163    return cmd[:pivot], cmd[pivot:]64 65 66def _split_aix(cmd):67    """68    AIX platforms prefix the compiler with the ld_so_aix69    script, so split that from the linker command.70 71    >>> _split_aix(['a', 'b', 'c'])72    ([], ['a', 'b', 'c'])73    >>> _split_aix(['/bin/foo/ld_so_aix', 'gcc'])74    (['/bin/foo/ld_so_aix'], ['gcc'])75    """76    pivot = os.path.basename(cmd[0]) == 'ld_so_aix'77    return cmd[:pivot], cmd[pivot:]78 79 80def _linker_params(linker_cmd, compiler_cmd):81    """82    The linker command usually begins with the compiler83    command (possibly multiple elements), followed by zero or more84    params for shared library building.85 86    If the LDSHARED env variable overrides the linker command,87    however, the commands may not match.88 89    Return the best guess of the linker parameters by stripping90    the linker command. If the compiler command does not91    match the linker command, assume the linker command is92    just the first element.93 94    >>> _linker_params('gcc foo bar'.split(), ['gcc'])95    ['foo', 'bar']96    >>> _linker_params('gcc foo bar'.split(), ['other'])97    ['foo', 'bar']98    >>> _linker_params('ccache gcc foo bar'.split(), 'ccache gcc'.split())99    ['foo', 'bar']100    >>> _linker_params(['gcc'], ['gcc'])101    []102    """103    c_len = len(compiler_cmd)104    pivot = c_len if linker_cmd[:c_len] == compiler_cmd else 1105    return linker_cmd[pivot:]106 107 108class UnixCCompiler(CCompiler):109    compiler_type = 'unix'110 111    # These are used by CCompiler in two places: the constructor sets112    # instance attributes 'preprocessor', 'compiler', etc. from them, and113    # 'set_executable()' allows any of these to be set.  The defaults here114    # are pretty generic; they will probably have to be set by an outsider115    # (eg. using information discovered by the sysconfig about building116    # Python extensions).117    executables = {118        'preprocessor': None,119        'compiler': ["cc"],120        'compiler_so': ["cc"],121        'compiler_cxx': ["cc"],122        'linker_so': ["cc", "-shared"],123        'linker_exe': ["cc"],124        'archiver': ["ar", "-cr"],125        'ranlib': None,126    }127 128    if sys.platform[:6] == "darwin":129        executables['ranlib'] = ["ranlib"]130 131    # Needed for the filename generation methods provided by the base132    # class, CCompiler.  NB. whoever instantiates/uses a particular133    # UnixCCompiler instance should set 'shared_lib_ext' -- we set a134    # reasonable common default here, but it's not necessarily used on all135    # Unices!136 137    src_extensions = [".c", ".C", ".cc", ".cxx", ".cpp", ".m"]138    obj_extension = ".o"139    static_lib_extension = ".a"140    shared_lib_extension = ".so"141    dylib_lib_extension = ".dylib"142    xcode_stub_lib_extension = ".tbd"143    static_lib_format = shared_lib_format = dylib_lib_format = "lib%s%s"144    xcode_stub_lib_format = dylib_lib_format145    if sys.platform == "cygwin":146        exe_extension = ".exe"147 148    def preprocess(149        self,150        source,151        output_file=None,152        macros=None,153        include_dirs=None,154        extra_preargs=None,155        extra_postargs=None,156    ):157        fixed_args = self._fix_compile_args(None, macros, include_dirs)158        ignore, macros, include_dirs = fixed_args159        pp_opts = gen_preprocess_options(macros, include_dirs)160        pp_args = self.preprocessor + pp_opts161        if output_file:162            pp_args.extend(['-o', output_file])163        if extra_preargs:164            pp_args[:0] = extra_preargs165        if extra_postargs:166            pp_args.extend(extra_postargs)167        pp_args.append(source)168 169        # reasons to preprocess:170        # - force is indicated171        # - output is directed to stdout172        # - source file is newer than the target173        preprocess = self.force or output_file is None or newer(source, output_file)174        if not preprocess:175            return176 177        if output_file:178            self.mkpath(os.path.dirname(output_file))179 180        try:181            self.spawn(pp_args)182        except DistutilsExecError as msg:183            raise CompileError(msg)184 185    def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts):186        compiler_so = compiler_fixup(self.compiler_so, cc_args + extra_postargs)187        try:188            self.spawn(compiler_so + cc_args + [src, '-o', obj] + extra_postargs)189        except DistutilsExecError as msg:190            raise CompileError(msg)191 192    def create_static_lib(193        self, objects, output_libname, output_dir=None, debug=0, target_lang=None194    ):195        objects, output_dir = self._fix_object_args(objects, output_dir)196 197        output_filename = self.library_filename(output_libname, output_dir=output_dir)198 199        if self._need_link(objects, output_filename):200            self.mkpath(os.path.dirname(output_filename))201            self.spawn(self.archiver + [output_filename] + objects + self.objects)202 203            # Not many Unices required ranlib anymore -- SunOS 4.x is, I204            # think the only major Unix that does.  Maybe we need some205            # platform intelligence here to skip ranlib if it's not206            # needed -- or maybe Python's configure script took care of207            # it for us, hence the check for leading colon.208            if self.ranlib:209                try:210                    self.spawn(self.ranlib + [output_filename])211                except DistutilsExecError as msg:212                    raise LibError(msg)213        else:214            log.debug("skipping %s (up-to-date)", output_filename)215 216    def link(217        self,218        target_desc,219        objects,220        output_filename,221        output_dir=None,222        libraries=None,223        library_dirs=None,224        runtime_library_dirs=None,225        export_symbols=None,226        debug=0,227        extra_preargs=None,228        extra_postargs=None,229        build_temp=None,230        target_lang=None,231    ):232        objects, output_dir = self._fix_object_args(objects, output_dir)233        fixed_args = self._fix_lib_args(libraries, library_dirs, runtime_library_dirs)234        libraries, library_dirs, runtime_library_dirs = fixed_args235 236        lib_opts = gen_lib_options(self, library_dirs, runtime_library_dirs, libraries)237        if not isinstance(output_dir, (str, type(None))):238            raise TypeError("'output_dir' must be a string or None")239        if output_dir is not None:240            output_filename = os.path.join(output_dir, output_filename)241 242        if self._need_link(objects, output_filename):243            ld_args = objects + self.objects + lib_opts + ['-o', output_filename]244            if debug:245                ld_args[:0] = ['-g']246            if extra_preargs:247                ld_args[:0] = extra_preargs248            if extra_postargs:249                ld_args.extend(extra_postargs)250            self.mkpath(os.path.dirname(output_filename))251            try:252                # Select a linker based on context: linker_exe when253                # building an executable or linker_so (with shared options)254                # when building a shared library.255                building_exe = target_desc == CCompiler.EXECUTABLE256                linker = (self.linker_exe if building_exe else self.linker_so)[:]257 258                if target_lang == "c++" and self.compiler_cxx:259                    env, linker_ne = _split_env(linker)260                    aix, linker_na = _split_aix(linker_ne)261                    _, compiler_cxx_ne = _split_env(self.compiler_cxx)262                    _, linker_exe_ne = _split_env(self.linker_exe)263 264                    params = _linker_params(linker_na, linker_exe_ne)265                    linker = env + aix + compiler_cxx_ne + params266 267                linker = compiler_fixup(linker, ld_args)268 269                self.spawn(linker + ld_args)270            except DistutilsExecError as msg:271                raise LinkError(msg)272        else:273            log.debug("skipping %s (up-to-date)", output_filename)274 275    # -- Miscellaneous methods -----------------------------------------276    # These are all used by the 'gen_lib_options() function, in277    # ccompiler.py.278 279    def library_dir_option(self, dir):280        return "-L" + dir281 282    def _is_gcc(self):283        cc_var = sysconfig.get_config_var("CC")284        compiler = os.path.basename(shlex.split(cc_var)[0])285        return "gcc" in compiler or "g++" in compiler286 287    def runtime_library_dir_option(self, dir: str) -> str | list[str]:288        # XXX Hackish, at the very least.  See Python bug #445902:289        # https://bugs.python.org/issue445902290        # Linkers on different platforms need different options to291        # specify that directories need to be added to the list of292        # directories searched for dependencies when a dynamic library293        # is sought.  GCC on GNU systems (Linux, FreeBSD, ...) has to294        # be told to pass the -R option through to the linker, whereas295        # other compilers and gcc on other systems just know this.296        # Other compilers may need something slightly different.  At297        # this time, there's no way to determine this information from298        # the configuration data stored in the Python installation, so299        # we use this hack.300        if sys.platform[:6] == "darwin":301            from distutils.util import get_macosx_target_ver, split_version302 303            macosx_target_ver = get_macosx_target_ver()304            if macosx_target_ver and split_version(macosx_target_ver) >= [10, 5]:305                return "-Wl,-rpath," + dir306            else:  # no support for -rpath on earlier macOS versions307                return "-L" + dir308        elif sys.platform[:7] == "freebsd":309            return "-Wl,-rpath=" + dir310        elif sys.platform[:5] == "hp-ux":311            return [312                "-Wl,+s" if self._is_gcc() else "+s",313                "-L" + dir,314            ]315 316        # For all compilers, `-Wl` is the presumed way to pass a317        # compiler option to the linker318        if sysconfig.get_config_var("GNULD") == "yes":319            return consolidate_linker_args([320                # Force RUNPATH instead of RPATH321                "-Wl,--enable-new-dtags",322                "-Wl,-rpath," + dir,323            ])324        else:325            return "-Wl,-R" + dir326 327    def library_option(self, lib):328        return "-l" + lib329 330    @staticmethod331    def _library_root(dir):332        """333        macOS users can specify an alternate SDK using'-isysroot'.334        Calculate the SDK root if it is specified.335 336        Note that, as of Xcode 7, Apple SDKs may contain textual stub337        libraries with .tbd extensions rather than the normal .dylib338        shared libraries installed in /.  The Apple compiler tool339        chain handles this transparently but it can cause problems340        for programs that are being built with an SDK and searching341        for specific libraries.  Callers of find_library_file need to342        keep in mind that the base filename of the returned SDK library343        file might have a different extension from that of the library344        file installed on the running system, for example:345          /Applications/Xcode.app/Contents/Developer/Platforms/346              MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/347              usr/lib/libedit.tbd348        vs349          /usr/lib/libedit.dylib350        """351        cflags = sysconfig.get_config_var('CFLAGS')352        match = re.search(r'-isysroot\s*(\S+)', cflags)353 354        apply_root = (355            sys.platform == 'darwin'356            and match357            and (358                dir.startswith('/System/')359                or (dir.startswith('/usr/') and not dir.startswith('/usr/local/'))360            )361        )362 363        return os.path.join(match.group(1), dir[1:]) if apply_root else dir364 365    def find_library_file(self, dirs, lib, debug=0):366        r"""367        Second-guess the linker with not much hard368        data to go on: GCC seems to prefer the shared library, so369        assume that *all* Unix C compilers do,370        ignoring even GCC's "-static" option.371 372        >>> compiler = UnixCCompiler()373        >>> compiler._library_root = lambda dir: dir374        >>> monkeypatch = getfixture('monkeypatch')375        >>> monkeypatch.setattr(os.path, 'exists', lambda d: 'existing' in d)376        >>> dirs = ('/foo/bar/missing', '/foo/bar/existing')377        >>> compiler.find_library_file(dirs, 'abc').replace('\\', '/')378        '/foo/bar/existing/libabc.dylib'379        >>> compiler.find_library_file(reversed(dirs), 'abc').replace('\\', '/')380        '/foo/bar/existing/libabc.dylib'381        >>> monkeypatch.setattr(os.path, 'exists',382        ...     lambda d: 'existing' in d and '.a' in d)383        >>> compiler.find_library_file(dirs, 'abc').replace('\\', '/')384        '/foo/bar/existing/libabc.a'385        >>> compiler.find_library_file(reversed(dirs), 'abc').replace('\\', '/')386        '/foo/bar/existing/libabc.a'387        """388        lib_names = (389            self.library_filename(lib, lib_type=type)390            for type in 'dylib xcode_stub shared static'.split()391        )392 393        roots = map(self._library_root, dirs)394 395        searched = itertools.starmap(os.path.join, itertools.product(roots, lib_names))396 397        found = filter(os.path.exists, searched)398 399        # Return None if it could not be found in any dir.400        return next(found, None)401 
codekingpro/portable-devtools · Team Ai