Commit fa88ad9d authored by Michael Davidsaver's avatar Michael Davidsaver

Merge branch 'probe'

* probe:
  handle strings in MSVC object files
  add toolchain inspection
parents 4fd53b95 bb62f5eb
......@@ -26,4 +26,5 @@ allowing non-python libraries to be built from source within a python ecosystem.
:maxdepth: 2
usage
probe
details
Probing Toolchain
=================
.. currentmodule:: setuptools_dso
Replacing buildsystems like autoconf or cmake may require compile time
inspection of the toolchain to determine sizes of types, the availability
of header files, or similar.
The `ProbeToolchain` class exists to allow these questions to be answered.
.. autoclass:: ProbeToolchain
:members:
#!/usr/bin/env python
from setuptools_dso import DSO, Extension, setup
from setuptools_dso.probe import ProbeToolchain
# example of inspecting the target toolchain
probe = ProbeToolchain()
assert probe.try_compile('#include <stdlib.h>')
assert not probe.try_compile('intentionally invalid syntax')
assert probe.check_include('stdlib.h')
assert not probe.check_include('no-such-header.h')
assert probe.sizeof('short')==2
assert probe.check_symbol('RAND_MAX', headers=['stdlib.h'])
assert probe.check_symbol('abort', headers=['stdlib.h'])
assert not probe.check_symbol('intentionally_undeclared_symbol', headers=['stdlib.h'])
dso = DSO('dsodemo.lib.demo', ['src/foo.c', 'src/bar.cpp'],
define_macros = [('BUILD_FOO', None)],
......
......@@ -5,6 +5,7 @@ import os
from setuptools import setup as _setup
from .dsocmd import DSO, Extension, build_dso, build_ext, bdist_egg
from .runtime import dylink_prepare_dso, find_dso
from .probe import ProbeToolchain
from setuptools.command.install import install
......@@ -17,6 +18,7 @@ __all__ = (
'setup',
'dylink_prepare_dso',
'find_dso',
'ProbeToolchain',
)
def setup(**kws):
......
import re
import os
import shutil
import tempfile
try:
from tempfile import TemporaryDirectory
except ImportError:
class TemporaryDirectory(object):
def __init__(self):
self.name = tempfile.mkdtemp()
def __del__(self):
self.cleanup()
def __enter__(self):
return self.name
def __exit__(self,A,B,C):
self.cleanup()
def cleanup(self):
if self.name is not None:
shutil.rmtree(self.name, ignore_errors=True)
self.name = None
from distutils.ccompiler import new_compiler
from distutils.sysconfig import customize_compiler
from distutils.errors import CompileError
from distutils import log
__all__ = (
'ProbeToolchain',
)
class ProbeToolchain(object):
"""Inspection of compiler
:param bool verbose: If True, enable additional prints
:param str compiler: If not None, select non-default compiler toolchain
:param list headers: List of headers to include during all test compilations
:param list define_macros: List of (macro, value) tuples to define during all test compilations
"""
def __init__(self, verbose=False,
compiler=None,
headers=[], define_macros=[]):
self.verbose = verbose
self.headers = list(headers)
self.define_macros = list(define_macros)
self.compiler = new_compiler(compiler=compiler,
verbose=self.verbose,
dry_run=False,
force=True)
customize_compiler(self.compiler)
# TODO: quiet compile errors?
self._tdir = TemporaryDirectory()
self.tempdir = self._tdir.name
def compile(self, src, language='c', define_macros=[], **kws):
"""Compile provided source code and return path to resulting object file
:returns: Path string to object file in temporary location.
:param str src: Source code string
:param str language: Source code language: 'c' or 'c++'
:param list define_macros: Extra macro definitions.
:param list include_dirs: Extra directories to search for headers
:param list extra_compile_args: Extra arguments to pass to the compiler
"""
define_macros = self.define_macros + list(define_macros)
for ext, lang in self.compiler.language_map.items():
if lang==language:
srcname = os.path.join(self.tempdir, 'try_compile' + ext)
break
else:
raise ValueError('unknown language '+language)
log.debug('/* test compile */\n'+src)
with open(srcname, 'w') as F:
F.write(src)
objs = self.compiler.compile([srcname],
output_dir=self.tempdir,
macros=define_macros,
**kws
)
assert len(objs)==1, (srcname, objs)
return objs[0]
def try_compile(self, src, **kws):
"""Return True if provided source code compiles
:param str src: Source code string
:param str language: Source code language: 'c' or 'c++'
:param list define_macros: Extra macro definitions.
:param list include_dirs: Extra directories to search for headers
:param list extra_compile_args: Extra arguments to pass to the compiler
"""
try:
self.compile(src, **kws)
return True
except CompileError as e:
return False
def check_includes(self, headers, **kws):
"""Return true if all of the headers may be included (in order)
:param list headers: List of header file names
:param str language: Source code language: 'c' or 'c++'
:param list define_macros: Extra macro definitions.
:param list include_dirs: Extra directories to search for headers
:param list extra_compile_args: Extra arguments to pass to the compiler
"""
src = ['#include <%s>'%h for h in self.headers+list(headers)]
return self.try_compile('\n'.join(src), **kws)
def check_include(self, header, **kws):
"""Return true if the header may be included
:param str header: Header file name
:param str language: Source code language: 'c' or 'c++'
:param list define_macros: Extra macro definitions.
:param list include_dirs: Extra directories to search for headers
:param list extra_compile_args: Extra arguments to pass to the compiler
"""
return self.check_includes([header], **kws)
def sizeof(self, typename, headers=(), **kws):
"""Return size in bytes of provided typename
:param str typename: Header file name
:param list headers: List of headers to include during all test compilations
:param str language: Source code language: 'c' or 'c++'
:param list define_macros: Extra macro definitions.
:param list include_dirs: Extra directories to search for headers
:param list extra_compile_args: Extra arguments to pass to the compiler
"""
# borrow a trick from CMake. see Modules/CheckTypeSize.c.in
src = ['#include <%s>'%h for h in self.headers+list(headers)]
src += [
'#define PROBESIZE (sizeof(%s))'%typename,
"char probe_info[] = {'P','R','O','B','E','I','N','F','O','[',"
" ('0'+((PROBESIZE/10000)%10)),"
" ('0'+((PROBESIZE/1000)%10)),"
" ('0'+((PROBESIZE/100)%10)),"
" ('0'+((PROBESIZE/10)%10)),"
" ('0'+((PROBESIZE/1)%10)),"
"']'};",
""
]
obj = self.compile('\n'.join(src), **kws)
with open(obj, 'rb') as F:
raw = F.read()
if raw.find(b'\x01\x01\x01P\x01\x01\x01R\x01\x01\x01O\x01\x01\x01B\x01\x01\x01E\x01\x01\x01I\x01\x01\x01N\x01\x01\x01F\x01\x01\x01O')!=-1:
# MSVC
raw = raw.replace(b'\x01\x01\x01', b'')
M = re.match(b'.*PROBEINFO\\[(\\d+)\\].*', raw, re.DOTALL)
if M is None:
print(repr(raw))
raise RuntimeError('Unable to find PROBEINFO for %s'%typename)
size = int(M.group(1))
return size
def check_symbol(self, symname, headers=(), **kws):
"""Return True if symbol name (macro, variable, or function) is defined/delcared
:param str symname: Symbol name
:param list headers: List of headers to include during all test compilations
:param str language: Source code language: 'c' or 'c++'
:param list define_macros: Extra macro definitions.
:param list include_dirs: Extra directories to search for headers
:param list extra_compile_args: Extra arguments to pass to the compiler
"""
src = ['#include <%s>'%h for h in self.headers+list(headers)]
src += [
'void* probe_symbol(void) {',
'#if defined(%s)'%symname,
' return 0;',
'#else',
' return (void*)&%s;'%symname,
'#endif',
'}',
''
]
return self.try_compile('\n'.join(src), **kws)
def check_member(self, struct, member, headers=(), **kws):
"""Return True if the given structure has the named member
:param str struct: Structure name
:param str member: Member name
:param list headers: List of headers to include during all test compilations
:param str language: Source code language: 'c' or 'c++'
:param list define_macros: Extra macro definitions.
:param list include_dirs: Extra directories to search for headers
:param list extra_compile_args: Extra arguments to pass to the compiler
"""
src = ['#include <%s>'%h for h in self.headers+list(headers)]
src += [
'int probe_member(void) {',
' return (int)sizeof( ((%s *)0)->%s); '%(struct, member),
'}',
''
]
return self.try_compile('\n'.join(src), **kws)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment