Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
config.py370 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 ..core import Command21from ..errors import DistutilsExecError22from ..sysconfig import customize_compiler23 24LANG_EXT = {"c": ".c", "c++": ".cxx"}25 26 27class config(Command):28    description = "prepare to build"29 30    user_options = [31        ('compiler=', None, "specify the compiler type"),32        ('cc=', None, "specify the compiler executable"),33        ('include-dirs=', 'I', "list of directories to search for header files"),34        ('define=', 'D', "C preprocessor macros to define"),35        ('undef=', 'U', "C preprocessor macros to undefine"),36        ('libraries=', 'l', "external C libraries to link with"),37        ('library-dirs=', 'L', "directories to search for external C libraries"),38        ('noisy', None, "show every action (compile, link, run, ...) taken"),39        (40            'dump-source',41            None,42            "dump generated source files before attempting to compile them",43        ),44    ]45 46    # The three standard command methods: since the "config" command47    # does nothing by default, these are empty.48 49    def initialize_options(self):50        self.compiler = None51        self.cc = None52        self.include_dirs = None53        self.libraries = None54        self.library_dirs = None55 56        # maximal output for now57        self.noisy = 158        self.dump_source = 159 60        # list of temporary files generated along-the-way that we have61        # to clean at some point62        self.temp_files = []63 64    def finalize_options(self):65        if self.include_dirs is None:66            self.include_dirs = self.distribution.include_dirs or []67        elif isinstance(self.include_dirs, str):68            self.include_dirs = self.include_dirs.split(os.pathsep)69 70        if self.libraries is None:71            self.libraries = []72        elif isinstance(self.libraries, str):73            self.libraries = [self.libraries]74 75        if self.library_dirs is None:76            self.library_dirs = []77        elif isinstance(self.library_dirs, str):78            self.library_dirs = self.library_dirs.split(os.pathsep)79 80    def run(self):81        pass82 83    # Utility methods for actual "config" commands.  The interfaces are84    # loosely based on Autoconf macros of similar names.  Sub-classes85    # may use these freely.86 87    def _check_compiler(self):88        """Check that 'self.compiler' really is a CCompiler object;89        if not, make it one.90        """91        # We do this late, and only on-demand, because this is an expensive92        # import.93        from ..ccompiler import CCompiler, new_compiler94 95        if not isinstance(self.compiler, CCompiler):96            self.compiler = new_compiler(97                compiler=self.compiler, dry_run=self.dry_run, force=198            )99            customize_compiler(self.compiler)100            if self.include_dirs:101                self.compiler.set_include_dirs(self.include_dirs)102            if self.libraries:103                self.compiler.set_libraries(self.libraries)104            if self.library_dirs:105                self.compiler.set_library_dirs(self.library_dirs)106 107    def _gen_temp_sourcefile(self, body, headers, lang):108        filename = "_configtest" + LANG_EXT[lang]109        with open(filename, "w", encoding='utf-8') as file:110            if headers:111                for header in headers:112                    file.write("#include <%s>\n" % header)113                file.write("\n")114            file.write(body)115            if body[-1] != "\n":116                file.write("\n")117        return filename118 119    def _preprocess(self, body, headers, include_dirs, lang):120        src = self._gen_temp_sourcefile(body, headers, lang)121        out = "_configtest.i"122        self.temp_files.extend([src, out])123        self.compiler.preprocess(src, out, include_dirs=include_dirs)124        return (src, out)125 126    def _compile(self, body, headers, include_dirs, lang):127        src = self._gen_temp_sourcefile(body, headers, lang)128        if self.dump_source:129            dump_file(src, "compiling '%s':" % src)130        (obj,) = self.compiler.object_filenames([src])131        self.temp_files.extend([src, obj])132        self.compiler.compile([src], include_dirs=include_dirs)133        return (src, obj)134 135    def _link(self, body, headers, include_dirs, libraries, library_dirs, lang):136        (src, obj) = self._compile(body, headers, include_dirs, lang)137        prog = os.path.splitext(os.path.basename(src))[0]138        self.compiler.link_executable(139            [obj],140            prog,141            libraries=libraries,142            library_dirs=library_dirs,143            target_lang=lang,144        )145 146        if self.compiler.exe_extension is not None:147            prog = prog + self.compiler.exe_extension148        self.temp_files.append(prog)149 150        return (src, obj, prog)151 152    def _clean(self, *filenames):153        if not filenames:154            filenames = self.temp_files155            self.temp_files = []156        log.info("removing: %s", ' '.join(filenames))157        for filename in filenames:158            try:159                os.remove(filename)160            except OSError:161                pass162 163    # XXX these ignore the dry-run flag: what to do, what to do? even if164    # you want a dry-run build, you still need some sort of configuration165    # info.  My inclination is to make it up to the real config command to166    # consult 'dry_run', and assume a default (minimal) configuration if167    # true.  The problem with trying to do it here is that you'd have to168    # return either true or false from all the 'try' methods, neither of169    # which is correct.170 171    # XXX need access to the header search path and maybe default macros.172 173    def try_cpp(self, body=None, headers=None, include_dirs=None, lang="c"):174        """Construct a source file from 'body' (a string containing lines175        of C/C++ code) and 'headers' (a list of header files to include)176        and run it through the preprocessor.  Return true if the177        preprocessor succeeded, false if there were any errors.178        ('body' probably isn't of much use, but what the heck.)179        """180        from ..ccompiler import CompileError181 182        self._check_compiler()183        ok = True184        try:185            self._preprocess(body, headers, include_dirs, lang)186        except CompileError:187            ok = False188 189        self._clean()190        return ok191 192    def search_cpp(self, pattern, body=None, headers=None, include_dirs=None, lang="c"):193        """Construct a source file (just like 'try_cpp()'), run it through194        the preprocessor, and return true if any line of the output matches195        'pattern'.  'pattern' should either be a compiled regex object or a196        string containing a regex.  If both 'body' and 'headers' are None,197        preprocesses an empty file -- which can be useful to determine the198        symbols the preprocessor and compiler set by default.199        """200        self._check_compiler()201        src, out = self._preprocess(body, headers, include_dirs, lang)202 203        if isinstance(pattern, str):204            pattern = re.compile(pattern)205 206        with open(out, encoding='utf-8') as file:207            match = any(pattern.search(line) for line in file)208 209        self._clean()210        return match211 212    def try_compile(self, body, headers=None, include_dirs=None, lang="c"):213        """Try to compile a source file built from 'body' and 'headers'.214        Return true on success, false otherwise.215        """216        from ..ccompiler import CompileError217 218        self._check_compiler()219        try:220            self._compile(body, headers, include_dirs, lang)221            ok = True222        except CompileError:223            ok = False224 225        log.info(ok and "success!" or "failure.")226        self._clean()227        return ok228 229    def try_link(230        self,231        body,232        headers=None,233        include_dirs=None,234        libraries=None,235        library_dirs=None,236        lang="c",237    ):238        """Try to compile and link a source file, built from 'body' and239        'headers', to executable form.  Return true on success, false240        otherwise.241        """242        from ..ccompiler import CompileError, LinkError243 244        self._check_compiler()245        try:246            self._link(body, headers, include_dirs, libraries, library_dirs, lang)247            ok = True248        except (CompileError, LinkError):249            ok = False250 251        log.info(ok and "success!" or "failure.")252        self._clean()253        return ok254 255    def try_run(256        self,257        body,258        headers=None,259        include_dirs=None,260        libraries=None,261        library_dirs=None,262        lang="c",263    ):264        """Try to compile, link to an executable, and run a program265        built from 'body' and 'headers'.  Return true on success, false266        otherwise.267        """268        from ..ccompiler import CompileError, LinkError269 270        self._check_compiler()271        try:272            src, obj, exe = self._link(273                body, headers, include_dirs, libraries, library_dirs, lang274            )275            self.spawn([exe])276            ok = True277        except (CompileError, LinkError, DistutilsExecError):278            ok = False279 280        log.info(ok and "success!" or "failure.")281        self._clean()282        return ok283 284    # -- High-level methods --------------------------------------------285    # (these are the ones that are actually likely to be useful286    # when implementing a real-world config command!)287 288    def check_func(289        self,290        func,291        headers=None,292        include_dirs=None,293        libraries=None,294        library_dirs=None,295        decl=0,296        call=0,297    ):298        """Determine if function 'func' is available by constructing a299        source file that refers to 'func', and compiles and links it.300        If everything succeeds, returns true; otherwise returns false.301 302        The constructed source file starts out by including the header303        files listed in 'headers'.  If 'decl' is true, it then declares304        'func' (as "int func()"); you probably shouldn't supply 'headers'305        and set 'decl' true in the same call, or you might get errors about306        a conflicting declarations for 'func'.  Finally, the constructed307        'main()' function either references 'func' or (if 'call' is true)308        calls it.  'libraries' and 'library_dirs' are used when309        linking.310        """311        self._check_compiler()312        body = []313        if decl:314            body.append("int %s ();" % func)315        body.append("int main () {")316        if call:317            body.append("  %s();" % func)318        else:319            body.append("  %s;" % func)320        body.append("}")321        body = "\n".join(body) + "\n"322 323        return self.try_link(body, headers, include_dirs, libraries, library_dirs)324 325    def check_lib(326        self,327        library,328        library_dirs=None,329        headers=None,330        include_dirs=None,331        other_libraries: Sequence[str] = [],332    ):333        """Determine if 'library' is available to be linked against,334        without actually checking that any particular symbols are provided335        by it.  'headers' will be used in constructing the source file to336        be compiled, but the only effect of this is to check if all the337        header files listed are available.  Any libraries listed in338        'other_libraries' will be included in the link, in case 'library'339        has symbols that depend on other libraries.340        """341        self._check_compiler()342        return self.try_link(343            "int main (void) { }",344            headers,345            include_dirs,346            [library] + list(other_libraries),347            library_dirs,348        )349 350    def check_header(self, header, include_dirs=None, library_dirs=None, lang="c"):351        """Determine if the system header file named by 'header_file'352        exists and can be found by the preprocessor; return true if so,353        false otherwise.354        """355        return self.try_cpp(356            body="/* No body */", headers=[header], include_dirs=include_dirs357        )358 359 360def dump_file(filename, head=None):361    """Dumps a file content into log.info.362 363    If head is not None, will be dumped before the file content.364    """365    if head is None:366        log.info('%s', filename)367    else:368        log.info(head)369    log.info(pathlib.Path(filename).read_text(encoding='utf-8'))370 
codekingpro/portable-devtools · Team Ai