1"""distutils.command.config
2
3Implements the Distutils 'config' command, a (mostly) empty command class
4that exists mainly to be sub-classed by specific module distributions and
5applications.  The idea is that while every "config" command is different,
6at least they're all named the same, and users always see "config" in the
7list of standard commands.  Also, this is a good place to put common
8configure-like tasks: "try to compile this C code", or "figure out where
9this header file lives".
10"""
11
12import os, re
13
14from distutils.core import Command
15from distutils.errors import DistutilsExecError
16from distutils.sysconfig import customize_compiler
17from distutils import log
18
19LANG_EXT = {"c": ".c", "c++": ".cxx"}
20
21class config(Command):
22
23    description = "prepare to build"
24
25    user_options = [
26        ('compiler=', None,
27         "specify the compiler type"),
28        ('cc=', None,
29         "specify the compiler executable"),
30        ('include-dirs=', 'I',
31         "list of directories to search for header files"),
32        ('define=', 'D',
33         "C preprocessor macros to define"),
34        ('undef=', 'U',
35         "C preprocessor macros to undefine"),
36        ('libraries=', 'l',
37         "external C libraries to link with"),
38        ('library-dirs=', 'L',
39         "directories to search for external C libraries"),
40
41        ('noisy', None,
42         "show every action (compile, link, run, ...) taken"),
43        ('dump-source', None,
44         "dump generated source files before attempting to compile them"),
45        ]
46
47
48    # The three standard command methods: since the "config" command
49    # does nothing by default, these are empty.
50
51    def initialize_options(self):
52        self.compiler = None
53        self.cc = None
54        self.include_dirs = None
55        self.libraries = None
56        self.library_dirs = None
57
58        # maximal output for now
59        self.noisy = 1
60        self.dump_source = 1
61
62        # list of temporary files generated along-the-way that we have
63        # to clean at some point
64        self.temp_files = []
65
66    def finalize_options(self):
67        if self.include_dirs is None:
68            self.include_dirs = self.distribution.include_dirs or []
69        elif isinstance(self.include_dirs, str):
70            self.include_dirs = self.include_dirs.split(os.pathsep)
71
72        if self.libraries is None:
73            self.libraries = []
74        elif isinstance(self.libraries, str):
75            self.libraries = [self.libraries]
76
77        if self.library_dirs is None:
78            self.library_dirs = []
79        elif isinstance(self.library_dirs, str):
80            self.library_dirs = self.library_dirs.split(os.pathsep)
81
82    def run(self):
83        pass
84
85    # Utility methods for actual "config" commands.  The interfaces are
86    # loosely based on Autoconf macros of similar names.  Sub-classes
87    # may use these freely.
88
89    def _check_compiler(self):
90        """Check that 'self.compiler' really is a CCompiler object;
91        if not, make it one.
92        """
93        # We do this late, and only on-demand, because this is an expensive
94        # import.
95        from distutils.ccompiler import CCompiler, new_compiler
96        if not isinstance(self.compiler, CCompiler):
97            self.compiler = new_compiler(compiler=self.compiler,
98                                         dry_run=self.dry_run, force=1)
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") 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 filename
118
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,
136              lang):
137        (src, obj) = self._compile(body, headers, include_dirs, lang)
138        prog = os.path.splitext(os.path.basename(src))[0]
139        self.compiler.link_executable([obj], prog,
140                                      libraries=libraries,
141                                      library_dirs=library_dirs,
142                                      target_lang=lang)
143
144        if self.compiler.exe_extension is not None:
145            prog = prog + self.compiler.exe_extension
146        self.temp_files.append(prog)
147
148        return (src, obj, prog)
149
150    def _clean(self, *filenames):
151        if not filenames:
152            filenames = self.temp_files
153            self.temp_files = []
154        log.info("removing: %s", ' '.join(filenames))
155        for filename in filenames:
156            try:
157                os.remove(filename)
158            except OSError:
159                pass
160
161
162    # XXX these ignore the dry-run flag: what to do, what to do? even if
163    # you want a dry-run build, you still need some sort of configuration
164    # info.  My inclination is to make it up to the real config command to
165    # consult 'dry_run', and assume a default (minimal) configuration if
166    # true.  The problem with trying to do it here is that you'd have to
167    # return either true or false from all the 'try' methods, neither of
168    # which is correct.
169
170    # XXX need access to the header search path and maybe default macros.
171
172    def try_cpp(self, body=None, headers=None, include_dirs=None, lang="c"):
173        """Construct a source file from 'body' (a string containing lines
174        of C/C++ code) and 'headers' (a list of header files to include)
175        and run it through the preprocessor.  Return true if the
176        preprocessor succeeded, false if there were any errors.
177        ('body' probably isn't of much use, but what the heck.)
178        """
179        from distutils.ccompiler import CompileError
180        self._check_compiler()
181        ok = True
182        try:
183            self._preprocess(body, headers, include_dirs, lang)
184        except CompileError:
185            ok = False
186
187        self._clean()
188        return ok
189
190    def search_cpp(self, pattern, body=None, headers=None, include_dirs=None,
191                   lang="c"):
192        """Construct a source file (just like 'try_cpp()'), run it through
193        the preprocessor, and return true if any line of the output matches
194        'pattern'.  'pattern' should either be a compiled regex object or a
195        string containing a regex.  If both 'body' and 'headers' are None,
196        preprocesses an empty file -- which can be useful to determine the
197        symbols the preprocessor and compiler set by default.
198        """
199        self._check_compiler()
200        src, out = self._preprocess(body, headers, include_dirs, lang)
201
202        if isinstance(pattern, str):
203            pattern = re.compile(pattern)
204
205        with open(out) as file:
206            match = False
207            while True:
208                line = file.readline()
209                if line == '':
210                    break
211                if pattern.search(line):
212                    match = True
213                    break
214
215        self._clean()
216        return match
217
218    def try_compile(self, body, headers=None, include_dirs=None, lang="c"):
219        """Try to compile a source file built from 'body' and 'headers'.
220        Return true on success, false otherwise.
221        """
222        from distutils.ccompiler import CompileError
223        self._check_compiler()
224        try:
225            self._compile(body, headers, include_dirs, lang)
226            ok = True
227        except CompileError:
228            ok = False
229
230        log.info(ok and "success!" or "failure.")
231        self._clean()
232        return ok
233
234    def try_link(self, body, headers=None, include_dirs=None, libraries=None,
235                 library_dirs=None, lang="c"):
236        """Try to compile and link a source file, built from 'body' and
237        'headers', to executable form.  Return true on success, false
238        otherwise.
239        """
240        from distutils.ccompiler import CompileError, LinkError
241        self._check_compiler()
242        try:
243            self._link(body, headers, include_dirs,
244                       libraries, library_dirs, lang)
245            ok = True
246        except (CompileError, LinkError):
247            ok = False
248
249        log.info(ok and "success!" or "failure.")
250        self._clean()
251        return ok
252
253    def try_run(self, body, headers=None, include_dirs=None, libraries=None,
254                library_dirs=None, lang="c"):
255        """Try to compile, link to an executable, and run a program
256        built from 'body' and 'headers'.  Return true on success, false
257        otherwise.
258        """
259        from distutils.ccompiler import CompileError, LinkError
260        self._check_compiler()
261        try:
262            src, obj, exe = self._link(body, headers, include_dirs,
263                                       libraries, library_dirs, lang)
264            self.spawn([exe])
265            ok = True
266        except (CompileError, LinkError, DistutilsExecError):
267            ok = False
268
269        log.info(ok and "success!" or "failure.")
270        self._clean()
271        return ok
272
273
274    # -- High-level methods --------------------------------------------
275    # (these are the ones that are actually likely to be useful
276    # when implementing a real-world config command!)
277
278    def check_func(self, func, headers=None, include_dirs=None,
279                   libraries=None, library_dirs=None, decl=0, call=0):
280        """Determine if function 'func' is available by constructing a
281        source file that refers to 'func', and compiles and links it.
282        If everything succeeds, returns true; otherwise returns false.
283
284        The constructed source file starts out by including the header
285        files listed in 'headers'.  If 'decl' is true, it then declares
286        'func' (as "int func()"); you probably shouldn't supply 'headers'
287        and set 'decl' true in the same call, or you might get errors about
288        a conflicting declarations for 'func'.  Finally, the constructed
289        'main()' function either references 'func' or (if 'call' is true)
290        calls it.  'libraries' and 'library_dirs' are used when
291        linking.
292        """
293        self._check_compiler()
294        body = []
295        if decl:
296            body.append("int %s ();" % func)
297        body.append("int main () {")
298        if call:
299            body.append("  %s();" % func)
300        else:
301            body.append("  %s;" % func)
302        body.append("}")
303        body = "\n".join(body) + "\n"
304
305        return self.try_link(body, headers, include_dirs,
306                             libraries, library_dirs)
307
308    def check_lib(self, library, library_dirs=None, headers=None,
309                  include_dirs=None, other_libraries=[]):
310        """Determine if 'library' is available to be linked against,
311        without actually checking that any particular symbols are provided
312        by it.  'headers' will be used in constructing the source file to
313        be compiled, but the only effect of this is to check if all the
314        header files listed are available.  Any libraries listed in
315        'other_libraries' will be included in the link, in case 'library'
316        has symbols that depend on other libraries.
317        """
318        self._check_compiler()
319        return self.try_link("int main (void) { }", headers, include_dirs,
320                             [library] + other_libraries, library_dirs)
321
322    def check_header(self, header, include_dirs=None, library_dirs=None,
323                     lang="c"):
324        """Determine if the system header file named by 'header_file'
325        exists and can be found by the preprocessor; return true if so,
326        false otherwise.
327        """
328        return self.try_cpp(body="/* No body */", headers=[header],
329                            include_dirs=include_dirs)
330
331def dump_file(filename, head=None):
332    """Dumps a file content into log.info.
333
334    If head is not None, will be dumped before the file content.
335    """
336    if head is None:
337        log.info('%s', filename)
338    else:
339        log.info(head)
340    file = open(filename)
341    try:
342        log.info(file.read())
343    finally:
344        file.close()
345