Fleshed out and added a bunch of useful stuff, notably 'check_func()',

'try_cpp()', 'search_cpp()', and 'check_header()'.  This is enough that
the base config is actually useful for implementing a real config
command, specifically one for mxDateTime.
This commit is contained in:
Greg Ward 2000-06-21 03:00:50 +00:00
parent 71257c73f8
commit 59ac7091a7
1 changed files with 187 additions and 43 deletions

View File

@ -13,7 +13,7 @@ this header file lives".
__revision__ = "$Id$"
import os, string
import sys, os, string, re
from distutils.core import Command
from distutils.errors import DistutilsExecError
@ -40,6 +40,11 @@ class config (Command):
"external C libraries to link with"),
('library-dirs=', 'L',
"directories to search for external C libraries"),
('noisy', None,
"show every action (compile, link, run, ...) taken"),
('dump-source', None,
"dump generated source files before attempting to compile them"),
]
@ -55,6 +60,14 @@ class config (Command):
self.libraries = None
self.library_dirs = None
# 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 = []
def finalize_options (self):
pass
@ -75,7 +88,7 @@ class config (Command):
from distutils.ccompiler import CCompiler, new_compiler
if not isinstance(self.compiler, CCompiler):
self.compiler = new_compiler (compiler=self.compiler,
verbose=self.verbose, # for now
verbose=self.noisy,
dry_run=self.dry_run,
force=1)
if self.include_dirs:
@ -86,25 +99,48 @@ class config (Command):
self.compiler.set_library_dirs(self.library_dirs)
def _gen_temp_sourcefile (self, body, lang):
def _gen_temp_sourcefile (self, body, headers, lang):
filename = "_configtest" + LANG_EXT[lang]
file = open(filename, "w")
if headers:
for header in headers:
file.write("#include <%s>\n" % header)
file.write("\n")
file.write(body)
if body[-1] != "\n":
file.write("\n")
file.close()
return filename
def _compile (self, body, lang):
src = self._gen_temp_sourcefile(body, lang)
(obj,) = self.compiler.compile([src])
def _preprocess (self, body, headers, lang):
src = self._gen_temp_sourcefile(body, headers, lang)
out = "_configtest.i"
self.temp_files.extend([src, out])
self.compiler.preprocess(src, out)
return (src, out)
def _compile (self, body, headers, lang):
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])
self.compiler.compile([src])
return (src, obj)
def _link (self, body, lang):
(src, obj) = self._compile(body, lang)
exe = os.path.splitext(os.path.basename(src))[0]
self.compiler.link_executable([obj], exe)
return (src, obj, exe)
def _link (self, body, headers, libraries, library_dirs, lang):
(src, obj) = self._compile(body, headers, lang)
prog = os.path.splitext(os.path.basename(src))[0]
self.temp_files.append(prog) # XXX should be prog + exe_ext
self.compiler.link_executable([obj], prog,
libraries=libraries,
library_dirs=library_dirs)
return (src, obj, prog)
def _clean (self, *filenames):
if not filenames:
filenames = self.temp_files
self.temp_files = []
self.announce("removing: " + string.join(filenames))
for filename in filenames:
try:
@ -113,10 +149,6 @@ class config (Command):
pass
# XXX no 'try_cpp()' or 'search_cpp()' since the CCompiler interface
# does not provide access to the preprocessor. This is an oversight
# that should be fixed.
# 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
@ -125,56 +157,168 @@ class config (Command):
# return either true or false from all the 'try' methods, neither of
# which is correct.
def try_compile (self, body, lang="c"):
"""Try to compile a source file that consists of the text in 'body'
(a multi-line string). Return true on success, false
otherwise.
# XXX need access to the header search path and maybe default macros.
def try_cpp (self, body=None, headers=None, lang="c"):
"""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.)
"""
from distutils.ccompiler import CompileError
self._check_compiler()
ok = 1
try:
self._preprocess(body, headers, lang)
except CompileError:
ok = 0
self._clean()
return ok
def search_cpp (self, pattern, body=None, headers=None, lang="c"):
"""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()
(src, out) = self._preprocess(body, headers, lang)
if type(pattern) is StringType:
pattern = re.compile(pattern)
file = open(out)
match = 0
while 1:
line = file.readline()
if line == '':
break
if pattern.search(pattern):
match = 1
break
file.close()
self._clean()
return match
def try_compile (self, body, headers=None, lang="c"):
"""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:
(src, obj) = self._compile(body, lang)
self._compile(body, headers, lang)
ok = 1
except CompileError:
ok = 0
self.announce(ok and "success!" or "failure.")
self._clean(src, obj)
self._clean()
return ok
def try_link (self, body, lang="c"):
"""Try to compile and link a source file (to an executable) that
consists of the text in 'body' (a multi-line string). Return true
on success, false otherwise.
"""
from distutils.ccompiler import CompileError, LinkError
self._check_compiler()
try:
(src, obj, exe) = self._link(body, lang)
ok = 1
except (CompileError, LinkError):
ok = 0
self.announce(ok and "success!" or "failure.")
self._clean(src, obj, exe)
return ok
def try_run (self, body, lang="c"):
"""Try to compile, link to an executable, and run a program that
consists of the text in 'body'. Return true on success, false
def try_link (self,
body, headers=None,
libraries=None, library_dirs=None,
lang="c"):
"""Try to compile and link a source file, built from 'body' and
'headers', to executable form. Return true on success, false
otherwise.
"""
from distutils.ccompiler import CompileError, LinkError
self._check_compiler()
try:
(src, obj, exe) = self._link(body, lang)
self._link(body, headers, libraries, library_dirs, lang)
ok = 1
except (CompileError, LinkError):
ok = 0
self.announce(ok and "success!" or "failure.")
self._clean()
return ok
def try_run (self,
body, headers=None,
libraries=None, library_dirs=None,
lang="c"):
"""Try to compile, link to an executable, and run a program
built from 'body' and 'headers'. Return true on success, false
otherwise.
"""
from distutils.ccompiler import CompileError, LinkError
self._check_compiler()
try:
self._link(body, headers, libraries, library_dirs, lang)
self.spawn([exe])
ok = 1
except (CompileError, LinkError, DistutilsExecError):
ok = 0
self.announce(ok and "success!" or "failure.")
self._clean(src, obj, exe)
self._clean()
return ok
# -- High-level methods --------------------------------------------
# (these are the ones that are actually likely to be useful
# when implementing a real-world config command!)
def check_func (self, func, headers=None,
libraries=None, library_dirs=None,
decl=0, call=0):
"""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("}")
body = string.join(body, "\n") + "\n"
return self.try_link(body, headers, libraries, library_dirs)
# check_func ()
def check_header (self, header, lang="c"):
"""Determine if the system header file named by 'header_file'
exists and can be found by the preprocessor; return true if so,
false otherwise.
"""
return self.try_cpp(headers=[header])
# class config
def dump_file (filename, head=None):
if head is None:
print filename + ":"
else:
print head
file = open(filename)
sys.stdout.write(file.read())
file.close()