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        file = open(filename, "w")
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        file.close()
118        return filename
119
120    def _preprocess(self, body, headers, include_dirs, lang):
121        src = self._gen_temp_sourcefile(body, headers, lang)
122        out = "_configtest.i"
123        self.temp_files.extend([src, out])
124        self.compiler.preprocess(src, out, include_dirs=include_dirs)
125        return (src, out)
126
127    def _compile(self, body, headers, include_dirs, lang):
128        src = self._gen_temp_sourcefile(body, headers, lang)
129        if self.dump_source:
130            dump_file(src, "compiling '%s':" % src)
131        (obj,) = self.compiler.object_filenames([src])
132        self.temp_files.extend([src, obj])
133        self.compiler.compile([src], include_dirs=include_dirs)
134        return (src, obj)
135
136    def _link(self, body, headers, include_dirs, libraries, library_dirs,
137              lang):
138        (src, obj) = self._compile(body, headers, include_dirs, lang)
139        prog = os.path.splitext(os.path.basename(src))[0]
140        self.compiler.link_executable([obj], prog,
141                                      libraries=libraries,
142                                      library_dirs=library_dirs,
143                                      target_lang=lang)
144
145        if self.compiler.exe_extension is not None:
146            prog = prog + self.compiler.exe_extension
147        self.temp_files.append(prog)
148
149        return (src, obj, prog)
150
151    def _clean(self, *filenames):
152        if not filenames:
153            filenames = self.temp_files
154            self.temp_files = []
155        log.info("removing: %s", ' '.join(filenames))
156        for filename in filenames:
157            try:
158                os.remove(filename)
159            except OSError:
160                pass
161
162
163    # XXX these ignore the dry-run flag: what to do, what to do? even if
164    # you want a dry-run build, you still need some sort of configuration
165    # info.  My inclination is to make it up to the real config command to
166    # consult 'dry_run', and assume a default (minimal) configuration if
167    # true.  The problem with trying to do it here is that you'd have to
168    # return either true or false from all the 'try' methods, neither of
169    # 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 lines
175        of C/C++ code) and 'headers' (a list of header files to include)
176        and run it through the preprocessor.  Return true if the
177        preprocessor succeeded, false if there were any errors.
178        ('body' probably isn't of much use, but what the heck.)
179        """
180        from distutils.ccompiler import CompileError
181        self._check_compiler()
182        ok = True
183        try:
184            self._preprocess(body, headers, include_dirs, lang)
185        except CompileError:
186            ok = False
187
188        self._clean()
189        return ok
190
191    def search_cpp(self, pattern, body=None, headers=None, include_dirs=None,
192                   lang="c"):
193        """Construct a source file (just like 'try_cpp()'), run it through
194        the preprocessor, and return true if any line of the output matches
195        'pattern'.  'pattern' should either be a compiled regex object or a
196        string containing a regex.  If both 'body' and 'headers' are None,
197        preprocesses an empty file -- which can be useful to determine the
198        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        file = open(out)
207        match = False
208        while True:
209            line = file.readline()
210            if line == '':
211                break
212            if pattern.search(line):
213                match = True
214                break
215
216        file.close()
217        self._clean()
218        return match
219
220    def try_compile(self, body, headers=None, include_dirs=None, lang="c"):
221        """Try to compile a source file built from 'body' and 'headers'.
222        Return true on success, false otherwise.
223        """
224        from distutils.ccompiler import CompileError
225        self._check_compiler()
226        try:
227            self._compile(body, headers, include_dirs, lang)
228            ok = True
229        except CompileError:
230            ok = False
231
232        log.info(ok and "success!" or "failure.")
233        self._clean()
234        return ok
235
236    def try_link(self, body, headers=None, include_dirs=None, libraries=None,
237                 library_dirs=None, lang="c"):
238        """Try to compile and link a source file, built from 'body' and
239        'headers', to executable form.  Return true on success, false
240        otherwise.
241        """
242        from distutils.ccompiler import CompileError, LinkError
243        self._check_compiler()
244        try:
245            self._link(body, headers, include_dirs,
246                       libraries, library_dirs, lang)
247            ok = True
248        except (CompileError, LinkError):
249            ok = False
250
251        log.info(ok and "success!" or "failure.")
252        self._clean()
253        return ok
254
255    def try_run(self, body, headers=None, include_dirs=None, libraries=None,
256                library_dirs=None, lang="c"):
257        """Try to compile, link to an executable, and run a program
258        built from 'body' and 'headers'.  Return true on success, false
259        otherwise.
260        """
261        from distutils.ccompiler import CompileError, LinkError
262        self._check_compiler()
263        try:
264            src, obj, exe = self._link(body, headers, include_dirs,
265                                       libraries, library_dirs, lang)
266            self.spawn([exe])
267            ok = True
268        except (CompileError, LinkError, DistutilsExecError):
269            ok = False
270
271        log.info(ok and "success!" or "failure.")
272        self._clean()
273        return ok
274
275
276    # -- High-level methods --------------------------------------------
277    # (these are the ones that are actually likely to be useful
278    # when implementing a real-world config command!)
279
280    def check_func(self, func, headers=None, include_dirs=None,
281                   libraries=None, library_dirs=None, decl=0, call=0):
282        """Determine if function 'func' is available by constructing a
283        source file that refers to 'func', and compiles and links it.
284        If everything succeeds, returns true; otherwise returns false.
285
286        The constructed source file starts out by including the header
287        files listed in 'headers'.  If 'decl' is true, it then declares
288        'func' (as "int func()"); you probably shouldn't supply 'headers'
289        and set 'decl' true in the same call, or you might get errors about
290        a conflicting declarations for 'func'.  Finally, the constructed
291        'main()' function either references 'func' or (if 'call' is true)
292        calls it.  'libraries' and 'library_dirs' are used when
293        linking.
294        """
295        self._check_compiler()
296        body = []
297        if decl:
298            body.append("int %s ();" % func)
299        body.append("int main () {")
300        if call:
301            body.append("  %s();" % func)
302        else:
303            body.append("  %s;" % func)
304        body.append("}")
305        body = "\n".join(body) + "\n"
306
307        return self.try_link(body, headers, include_dirs,
308                             libraries, library_dirs)
309
310    def check_lib(self, library, library_dirs=None, headers=None,
311                  include_dirs=None, other_libraries=[]):
312        """Determine if 'library' is available to be linked against,
313        without actually checking that any particular symbols are provided
314        by it.  'headers' will be used in constructing the source file to
315        be compiled, but the only effect of this is to check if all the
316        header files listed are available.  Any libraries listed in
317        '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("int main (void) { }", headers, include_dirs,
322                             [library] + other_libraries, library_dirs)
323
324    def check_header(self, header, include_dirs=None, library_dirs=None,
325                     lang="c"):
326        """Determine if the system header file named by 'header_file'
327        exists and can be found by the preprocessor; return true if so,
328        false otherwise.
329        """
330        return self.try_cpp(body="/* No body */", headers=[header],
331                            include_dirs=include_dirs)
332
333
334def dump_file(filename, head=None):
335    """Dumps a file content into log.info.
336
337    If head is not None, will be dumped before the file content.
338    """
339    if head is None:
340        log.info('%s', filename)
341    else:
342        log.info(head)
343    file = open(filename)
344    try:
345        log.info(file.read())
346    finally:
347        file.close()
348