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