Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
config.py349 linesDownload Raw Back to command
1"""distutils.command.config2 3Implements the Distutils 'config' command, a (mostly) empty command class4that exists mainly to be sub-classed by specific module distributions and5applications.  The idea is that while every "config" command is different,6at least they're all named the same, and users always see "config" in the7list of standard commands.  Also, this is a good place to put common8configure-like tasks: "try to compile this C code", or "figure out where9this header file lives".10"""11 12from __future__ import annotations13 14import os15import pathlib16import re17from collections.abc import Sequence18from distutils._log import log19 20from ..ccompiler import CCompiler, CompileError, LinkError, new_compiler21from ..core import Command22from ..errors import DistutilsExecError23from ..sysconfig import customize_compiler24 25LANG_EXT = {"c": ".c", "c++": ".cxx"}26 27 28class config(Command):29    description = "prepare to build"30 31    user_options = [32        ('compiler=', None, "specify the compiler type"),33        ('cc=', None, "specify the compiler executable"),34        ('include-dirs=', 'I', "list of directories to search for header files"),35        ('define=', 'D', "C preprocessor macros to define"),36        ('undef=', 'U', "C preprocessor macros to undefine"),37        ('libraries=', 'l', "external C libraries to link with"),38        ('library-dirs=', 'L', "directories to search for external C libraries"),39        ('noisy', None, "show every action (compile, link, run, ...) taken"),40        (41            'dump-source',42            None,43            "dump generated source files before attempting to compile them",44        ),45    ]46 47    # The three standard command methods: since the "config" command48    # does nothing by default, these are empty.49 50    def initialize_options(self):51        self.compiler = None52        self.cc = None53        self.include_dirs = None54        self.libraries = None55        self.library_dirs = None56 57        # maximal output for now58        self.noisy = 159        self.dump_source = 160 61        # list of temporary files generated along-the-way that we have62        # to clean at some point63        self.temp_files = []64 65    def finalize_options(self):66        if self.include_dirs is None:67            self.include_dirs = self.distribution.include_dirs or []68        elif isinstance(self.include_dirs, str):69            self.include_dirs = self.include_dirs.split(os.pathsep)70 71        if self.libraries is None:72            self.libraries = []73        elif isinstance(self.libraries, str):74            self.libraries = [self.libraries]75 76        if self.library_dirs is None:77            self.library_dirs = []78        elif isinstance(self.library_dirs, str):79            self.library_dirs = self.library_dirs.split(os.pathsep)80 81    def run(self):82        pass83 84    # Utility methods for actual "config" commands.  The interfaces are85    # loosely based on Autoconf macros of similar names.  Sub-classes86    # may use these freely.87 88    def _check_compiler(self):89        """Check that 'self.compiler' really is a CCompiler object;90        if not, make it one.91        """92        if not isinstance(self.compiler, CCompiler):93            self.compiler = new_compiler(compiler=self.compiler, force=True)94            customize_compiler(self.compiler)95            if self.include_dirs:96                self.compiler.set_include_dirs(self.include_dirs)97            if self.libraries:98                self.compiler.set_libraries(self.libraries)99            if self.library_dirs:100                self.compiler.set_library_dirs(self.library_dirs)101 102    def _gen_temp_sourcefile(self, body, headers, lang):103        filename = "_configtest" + LANG_EXT[lang]104        with open(filename, "w", encoding='utf-8') as file:105            if headers:106                for header in headers:107                    file.write(f"#include <{header}>\n")108                file.write("\n")109            file.write(body)110            if body[-1] != "\n":111                file.write("\n")112        return filename113 114    def _preprocess(self, body, headers, include_dirs, lang):115        src = self._gen_temp_sourcefile(body, headers, lang)116        out = "_configtest.i"117        self.temp_files.extend([src, out])118        self.compiler.preprocess(src, out, include_dirs=include_dirs)119        return (src, out)120 121    def _compile(self, body, headers, include_dirs, lang):122        src = self._gen_temp_sourcefile(body, headers, lang)123        if self.dump_source:124            dump_file(src, f"compiling '{src}':")125        (obj,) = self.compiler.object_filenames([src])126        self.temp_files.extend([src, obj])127        self.compiler.compile([src], include_dirs=include_dirs)128        return (src, obj)129 130    def _link(self, body, headers, include_dirs, libraries, library_dirs, lang):131        (src, obj) = self._compile(body, headers, include_dirs, lang)132        prog = os.path.splitext(os.path.basename(src))[0]133        self.compiler.link_executable(134            [obj],135            prog,136            libraries=libraries,137            library_dirs=library_dirs,138            target_lang=lang,139        )140 141        if self.compiler.exe_extension is not None:142            prog = prog + self.compiler.exe_extension143        self.temp_files.append(prog)144 145        return (src, obj, prog)146 147    def _clean(self, *filenames):148        if not filenames:149            filenames = self.temp_files150            self.temp_files = []151        log.info("removing: %s", ' '.join(filenames))152        for filename in filenames:153            try:154                os.remove(filename)155            except OSError:156                pass157 158    # XXX need access to the header search path and maybe default macros.159 160    def try_cpp(self, body=None, headers=None, include_dirs=None, lang="c"):161        """Construct a source file from 'body' (a string containing lines162        of C/C++ code) and 'headers' (a list of header files to include)163        and run it through the preprocessor.  Return true if the164        preprocessor succeeded, false if there were any errors.165        ('body' probably isn't of much use, but what the heck.)166        """167        self._check_compiler()168        ok = True169        try:170            self._preprocess(body, headers, include_dirs, lang)171        except CompileError:172            ok = False173 174        self._clean()175        return ok176 177    def search_cpp(self, pattern, body=None, headers=None, include_dirs=None, lang="c"):178        """Construct a source file (just like 'try_cpp()'), run it through179        the preprocessor, and return true if any line of the output matches180        'pattern'.  'pattern' should either be a compiled regex object or a181        string containing a regex.  If both 'body' and 'headers' are None,182        preprocesses an empty file -- which can be useful to determine the183        symbols the preprocessor and compiler set by default.184        """185        self._check_compiler()186        src, out = self._preprocess(body, headers, include_dirs, lang)187 188        if isinstance(pattern, str):189            pattern = re.compile(pattern)190 191        with open(out, encoding='utf-8') as file:192            match = any(pattern.search(line) for line in file)193 194        self._clean()195        return match196 197    def try_compile(self, body, headers=None, include_dirs=None, lang="c"):198        """Try to compile a source file built from 'body' and 'headers'.199        Return true on success, false otherwise.200        """201        self._check_compiler()202        try:203            self._compile(body, headers, include_dirs, lang)204            ok = True205        except CompileError:206            ok = False207 208        log.info(ok and "success!" or "failure.")209        self._clean()210        return ok211 212    def try_link(213        self,214        body,215        headers=None,216        include_dirs=None,217        libraries=None,218        library_dirs=None,219        lang="c",220    ):221        """Try to compile and link a source file, built from 'body' and222        'headers', to executable form.  Return true on success, false223        otherwise.224        """225        self._check_compiler()226        try:227            self._link(body, headers, include_dirs, libraries, library_dirs, lang)228            ok = True229        except (CompileError, LinkError):230            ok = False231 232        log.info(ok and "success!" or "failure.")233        self._clean()234        return ok235 236    def try_run(237        self,238        body,239        headers=None,240        include_dirs=None,241        libraries=None,242        library_dirs=None,243        lang="c",244    ):245        """Try to compile, link to an executable, and run a program246        built from 'body' and 'headers'.  Return true on success, false247        otherwise.248        """249        self._check_compiler()250        try:251            src, obj, exe = self._link(252                body, headers, include_dirs, libraries, library_dirs, lang253            )254            self.spawn([exe])255            ok = True256        except (CompileError, LinkError, DistutilsExecError):257            ok = False258 259        log.info(ok and "success!" or "failure.")260        self._clean()261        return ok262 263    # -- High-level methods --------------------------------------------264    # (these are the ones that are actually likely to be useful265    # when implementing a real-world config command!)266 267    def check_func(268        self,269        func,270        headers=None,271        include_dirs=None,272        libraries=None,273        library_dirs=None,274        decl=False,275        call=False,276    ):277        """Determine if function 'func' is available by constructing a278        source file that refers to 'func', and compiles and links it.279        If everything succeeds, returns true; otherwise returns false.280 281        The constructed source file starts out by including the header282        files listed in 'headers'.  If 'decl' is true, it then declares283        'func' (as "int func()"); you probably shouldn't supply 'headers'284        and set 'decl' true in the same call, or you might get errors about285        a conflicting declarations for 'func'.  Finally, the constructed286        'main()' function either references 'func' or (if 'call' is true)287        calls it.  'libraries' and 'library_dirs' are used when288        linking.289        """290        self._check_compiler()291        body = []292        if decl:293            body.append(f"int {func} ();")294        body.append("int main () {")295        if call:296            body.append(f"  {func}();")297        else:298            body.append(f"  {func};")299        body.append("}")300        body = "\n".join(body) + "\n"301 302        return self.try_link(body, headers, include_dirs, libraries, library_dirs)303 304    def check_lib(305        self,306        library,307        library_dirs=None,308        headers=None,309        include_dirs=None,310        other_libraries: Sequence[str] = [],311    ):312        """Determine if 'library' is available to be linked against,313        without actually checking that any particular symbols are provided314        by it.  'headers' will be used in constructing the source file to315        be compiled, but the only effect of this is to check if all the316        header files listed are available.  Any libraries listed in317        'other_libraries' will be included in the link, in case 'library'318        has symbols that depend on other libraries.319        """320        self._check_compiler()321        return self.try_link(322            "int main (void) { }",323            headers,324            include_dirs,325            [library] + list(other_libraries),326            library_dirs,327        )328 329    def check_header(self, header, include_dirs=None, library_dirs=None, lang="c"):330        """Determine if the system header file named by 'header_file'331        exists and can be found by the preprocessor; return true if so,332        false otherwise.333        """334        return self.try_cpp(335            body="/* No body */", headers=[header], include_dirs=include_dirs336        )337 338 339def dump_file(filename, head=None):340    """Dumps a file content into log.info.341 342    If head is not None, will be dumped before the file content.343    """344    if head is None:345        log.info('%s', filename)346    else:347        log.info(head)348    log.info(pathlib.Path(filename).read_text(encoding='utf-8'))349 
codekingpro/portable-devtools · Team Ai