config.py 12.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11
"""distutils.command.config

Implements the Distutils 'config' command, a (mostly) empty command class
that exists mainly to be sub-classed by specific module distributions and
applications.  The idea is that while every "config" command is different,
at least they're all named the same, and users always see "config" in the
list of standard commands.  Also, this is a good place to put common
configure-like tasks: "try to compile this C code", or "figure out where
this header file lives".
"""

12
import os, re
13

14 15
from distutils.core import Command
from distutils.errors import DistutilsExecError
16
from distutils.sysconfig import customize_compiler
17
from distutils import log
18

19
LANG_EXT = {"c": ".c", "c++": ".cxx"}
20

21
class config(Command):
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39

    description = "prepare to build"

    user_options = [
        ('compiler=', None,
         "specify the compiler type"),
        ('cc=', None,
         "specify the compiler executable"),
        ('include-dirs=', 'I',
         "list of directories to search for header files"),
        ('define=', 'D',
         "C preprocessor macros to define"),
        ('undef=', 'U',
         "C preprocessor macros to undefine"),
        ('libraries=', 'l',
         "external C libraries to link with"),
        ('library-dirs=', 'L',
         "directories to search for external C libraries"),
40 41 42 43 44

        ('noisy', None,
         "show every action (compile, link, run, ...) taken"),
        ('dump-source', None,
         "dump generated source files before attempting to compile them"),
45 46 47 48 49 50
        ]


    # The three standard command methods: since the "config" command
    # does nothing by default, these are empty.

51
    def initialize_options(self):
52 53 54 55 56 57
        self.compiler = None
        self.cc = None
        self.include_dirs = None
        self.libraries = None
        self.library_dirs = None

58 59 60 61 62 63 64 65
        # maximal output for now
        self.noisy = 1
        self.dump_source = 1

        # list of temporary files generated along-the-way that we have
        # to clean at some point
        self.temp_files = []

66
    def finalize_options(self):
67 68
        if self.include_dirs is None:
            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 73

        if self.libraries is None:
            self.libraries = []
74
        elif isinstance(self.libraries, str):
75 76 77 78
            self.libraries = [self.libraries]

        if self.library_dirs is None:
            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 84 85 86 87 88
        pass

    # Utility methods for actual "config" commands.  The interfaces are
    # loosely based on Autoconf macros of similar names.  Sub-classes
    # may use these freely.

89
    def _check_compiler(self):
90 91 92 93 94 95 96
        """Check that 'self.compiler' really is a CCompiler object;
        if not, make it one.
        """
        # We do this late, and only on-demand, because this is an expensive
        # import.
        from distutils.ccompiler import CCompiler, new_compiler
        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 101 102 103 104 105 106
            if self.include_dirs:
                self.compiler.set_include_dirs(self.include_dirs)
            if self.libraries:
                self.compiler.set_libraries(self.libraries)
            if self.library_dirs:
                self.compiler.set_library_dirs(self.library_dirs)

107
    def _gen_temp_sourcefile(self, body, headers, lang):
108 109
        filename = "_configtest" + LANG_EXT[lang]
        file = open(filename, "w")
110 111 112 113
        if headers:
            for header in headers:
                file.write("#include <%s>\n" % header)
            file.write("\n")
114
        file.write(body)
115 116
        if body[-1] != "\n":
            file.write("\n")
117 118 119
        file.close()
        return filename

120
    def _preprocess(self, body, headers, include_dirs, lang):
121 122 123
        src = self._gen_temp_sourcefile(body, headers, lang)
        out = "_configtest.i"
        self.temp_files.extend([src, out])
124
        self.compiler.preprocess(src, out, include_dirs=include_dirs)
125 126
        return (src, out)

127
    def _compile(self, body, headers, include_dirs, lang):
128 129 130 131 132
        src = self._gen_temp_sourcefile(body, headers, lang)
        if self.dump_source:
            dump_file(src, "compiling '%s':" % src)
        (obj,) = self.compiler.object_filenames([src])
        self.temp_files.extend([src, obj])
133
        self.compiler.compile([src], include_dirs=include_dirs)
134 135
        return (src, obj)

136 137
    def _link(self, body, headers, include_dirs, libraries, library_dirs,
              lang):
138
        (src, obj) = self._compile(body, headers, include_dirs, lang)
Fred Drake's avatar
Fred Drake committed
139
        prog = os.path.splitext(os.path.basename(src))[0]
140 141
        self.compiler.link_executable([obj], prog,
                                      libraries=libraries,
142 143
                                      library_dirs=library_dirs,
                                      target_lang=lang)
144

145 146
        if self.compiler.exe_extension is not None:
            prog = prog + self.compiler.exe_extension
147 148
        self.temp_files.append(prog)

149
        return (src, obj, prog)
150

151
    def _clean(self, *filenames):
152 153 154
        if not filenames:
            filenames = self.temp_files
            self.temp_files = []
155
        log.info("removing: %s", ' '.join(filenames))
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
        for filename in filenames:
            try:
                os.remove(filename)
            except OSError:
                pass


    # XXX these ignore the dry-run flag: what to do, what to do? even if
    # you want a dry-run build, you still need some sort of configuration
    # info.  My inclination is to make it up to the real config command to
    # consult 'dry_run', and assume a default (minimal) configuration if
    # true.  The problem with trying to do it here is that you'd have to
    # return either true or false from all the 'try' methods, neither of
    # which is correct.

171 172
    # XXX need access to the header search path and maybe default macros.

173
    def try_cpp(self, body=None, headers=None, include_dirs=None, lang="c"):
174 175 176 177 178
        """Construct a source file from 'body' (a string containing lines
        of C/C++ code) and 'headers' (a list of header files to include)
        and run it through the preprocessor.  Return true if the
        preprocessor succeeded, false if there were any errors.
        ('body' probably isn't of much use, but what the heck.)
179 180 181
        """
        from distutils.ccompiler import CompileError
        self._check_compiler()
182
        ok = True
183
        try:
184
            self._preprocess(body, headers, include_dirs, lang)
185
        except CompileError:
186
            ok = False
187 188 189 190

        self._clean()
        return ok

191 192
    def search_cpp(self, pattern, body=None, headers=None, include_dirs=None,
                   lang="c"):
193 194 195 196 197 198 199 200
        """Construct a source file (just like 'try_cpp()'), run it through
        the preprocessor, and return true if any line of the output matches
        'pattern'.  'pattern' should either be a compiled regex object or a
        string containing a regex.  If both 'body' and 'headers' are None,
        preprocesses an empty file -- which can be useful to determine the
        symbols the preprocessor and compiler set by default.
        """
        self._check_compiler()
201
        src, out = self._preprocess(body, headers, include_dirs, lang)
202

203
        if isinstance(pattern, str):
204 205 206
            pattern = re.compile(pattern)

        file = open(out)
207 208
        match = False
        while True:
209 210 211
            line = file.readline()
            if line == '':
                break
212
            if pattern.search(line):
213
                match = True
214 215 216 217 218 219
                break

        file.close()
        self._clean()
        return match

220
    def try_compile(self, body, headers=None, include_dirs=None, lang="c"):
221 222 223 224 225 226
        """Try to compile a source file built from 'body' and 'headers'.
        Return true on success, false otherwise.
        """
        from distutils.ccompiler import CompileError
        self._check_compiler()
        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 235
        return ok

236 237
    def try_link(self, body, headers=None, include_dirs=None, libraries=None,
                 library_dirs=None, lang="c"):
238 239 240
        """Try to compile and link a source file, built from 'body' and
        'headers', to executable form.  Return true on success, false
        otherwise.
241 242 243 244
        """
        from distutils.ccompiler import CompileError, LinkError
        self._check_compiler()
        try:
245 246
            self._link(body, headers, include_dirs,
                       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
Fred Drake's avatar
Fred Drake committed
254

255 256
    def try_run(self, body, headers=None, include_dirs=None, libraries=None,
                library_dirs=None, lang="c"):
257 258
        """Try to compile, link to an executable, and run a program
        built from 'body' and 'headers'.  Return true on success, false
259 260 261 262 263
        otherwise.
        """
        from distutils.ccompiler import CompileError, LinkError
        self._check_compiler()
        try:
264 265
            src, obj, exe = self._link(body, headers, include_dirs,
                                       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 274
        return ok

275 276 277 278 279

    # -- High-level methods --------------------------------------------
    # (these are the ones that are actually likely to be useful
    # when implementing a real-world config command!)

280 281
    def check_func(self, func, headers=None, include_dirs=None,
                   libraries=None, library_dirs=None, decl=0, call=0):
282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304
        """Determine if function 'func' is available by constructing a
        source file that refers to 'func', and compiles and links it.
        If everything succeeds, returns true; otherwise returns false.

        The constructed source file starts out by including the header
        files listed in 'headers'.  If 'decl' is true, it then declares
        'func' (as "int func()"); you probably shouldn't supply 'headers'
        and set 'decl' true in the same call, or you might get errors about
        a conflicting declarations for 'func'.  Finally, the constructed
        'main()' function either references 'func' or (if 'call' is true)
        calls it.  'libraries' and 'library_dirs' are used when
        linking.
        """
        self._check_compiler()
        body = []
        if decl:
            body.append("int %s ();" % func)
        body.append("int main () {")
        if call:
            body.append("  %s();" % func)
        else:
            body.append("  %s;" % func)
        body.append("}")
305
        body = "\n".join(body) + "\n"
306

307 308
        return self.try_link(body, headers, include_dirs,
                             libraries, library_dirs)
309

310 311
    def check_lib(self, library, library_dirs=None, headers=None,
                  include_dirs=None, other_libraries=[]):
312 313 314 315
        """Determine if 'library' is available to be linked against,
        without actually checking that any particular symbols are provided
        by it.  'headers' will be used in constructing the source file to
        be compiled, but the only effect of this is to check if all the
316 317 318
        header files listed are available.  Any libraries listed in
        'other_libraries' will be included in the link, in case 'library'
        has symbols that depend on other libraries.
319 320
        """
        self._check_compiler()
321 322
        return self.try_link("int main (void) { }", headers, include_dirs,
                             [library] + other_libraries, library_dirs)
323

324 325
    def check_header(self, header, include_dirs=None, library_dirs=None,
                     lang="c"):
326 327 328 329
        """Determine if the system header file named by 'header_file'
        exists and can be found by the preprocessor; return true if so,
        false otherwise.
        """
330 331
        return self.try_cpp(body="/* No body */", headers=[header],
                            include_dirs=include_dirs)
332 333


334
def dump_file(filename, head=None):
335 336 337 338
    """Dumps a file content into log.info.

    If head is not None, will be dumped before the file content.
    """
339
    if head is None:
340
        log.info('%s', filename)
341
    else:
342
        log.info(head)
343
    file = open(filename)
344 345 346 347
    try:
        log.info(file.read())
    finally:
        file.close()