Spaces:
Runtime error
Runtime error
# pkg-config, https://www.freedesktop.org/wiki/Software/pkg-config/ integration for cffi | |
import sys, os, subprocess | |
from .error import PkgConfigError | |
def merge_flags(cfg1, cfg2): | |
"""Merge values from cffi config flags cfg2 to cf1 | |
Example: | |
merge_flags({"libraries": ["one"]}, {"libraries": ["two"]}) | |
{"libraries": ["one", "two"]} | |
""" | |
for key, value in cfg2.items(): | |
if key not in cfg1: | |
cfg1[key] = value | |
else: | |
if not isinstance(cfg1[key], list): | |
raise TypeError("cfg1[%r] should be a list of strings" % (key,)) | |
if not isinstance(value, list): | |
raise TypeError("cfg2[%r] should be a list of strings" % (key,)) | |
cfg1[key].extend(value) | |
return cfg1 | |
def call(libname, flag, encoding=sys.getfilesystemencoding()): | |
"""Calls pkg-config and returns the output if found | |
""" | |
a = ["pkg-config", "--print-errors"] | |
a.append(flag) | |
a.append(libname) | |
try: | |
pc = subprocess.Popen(a, stdout=subprocess.PIPE, stderr=subprocess.PIPE) | |
except EnvironmentError as e: | |
raise PkgConfigError("cannot run pkg-config: %s" % (str(e).strip(),)) | |
bout, berr = pc.communicate() | |
if pc.returncode != 0: | |
try: | |
berr = berr.decode(encoding) | |
except Exception: | |
pass | |
raise PkgConfigError(berr.strip()) | |
if sys.version_info >= (3,) and not isinstance(bout, str): # Python 3.x | |
try: | |
bout = bout.decode(encoding) | |
except UnicodeDecodeError: | |
raise PkgConfigError("pkg-config %s %s returned bytes that cannot " | |
"be decoded with encoding %r:\n%r" % | |
(flag, libname, encoding, bout)) | |
if os.altsep != '\\' and '\\' in bout: | |
raise PkgConfigError("pkg-config %s %s returned an unsupported " | |
"backslash-escaped output:\n%r" % | |
(flag, libname, bout)) | |
return bout | |
def flags_from_pkgconfig(libs): | |
r"""Return compiler line flags for FFI.set_source based on pkg-config output | |
Usage | |
... | |
ffibuilder.set_source("_foo", pkgconfig = ["libfoo", "libbar >= 1.8.3"]) | |
If pkg-config is installed on build machine, then arguments include_dirs, | |
library_dirs, libraries, define_macros, extra_compile_args and | |
extra_link_args are extended with an output of pkg-config for libfoo and | |
libbar. | |
Raises PkgConfigError in case the pkg-config call fails. | |
""" | |
def get_include_dirs(string): | |
return [x[2:] for x in string.split() if x.startswith("-I")] | |
def get_library_dirs(string): | |
return [x[2:] for x in string.split() if x.startswith("-L")] | |
def get_libraries(string): | |
return [x[2:] for x in string.split() if x.startswith("-l")] | |
# convert -Dfoo=bar to list of tuples [("foo", "bar")] expected by distutils | |
def get_macros(string): | |
def _macro(x): | |
x = x[2:] # drop "-D" | |
if '=' in x: | |
return tuple(x.split("=", 1)) # "-Dfoo=bar" => ("foo", "bar") | |
else: | |
return (x, None) # "-Dfoo" => ("foo", None) | |
return [_macro(x) for x in string.split() if x.startswith("-D")] | |
def get_other_cflags(string): | |
return [x for x in string.split() if not x.startswith("-I") and | |
not x.startswith("-D")] | |
def get_other_libs(string): | |
return [x for x in string.split() if not x.startswith("-L") and | |
not x.startswith("-l")] | |
# return kwargs for given libname | |
def kwargs(libname): | |
fse = sys.getfilesystemencoding() | |
all_cflags = call(libname, "--cflags") | |
all_libs = call(libname, "--libs") | |
return { | |
"include_dirs": get_include_dirs(all_cflags), | |
"library_dirs": get_library_dirs(all_libs), | |
"libraries": get_libraries(all_libs), | |
"define_macros": get_macros(all_cflags), | |
"extra_compile_args": get_other_cflags(all_cflags), | |
"extra_link_args": get_other_libs(all_libs), | |
} | |
# merge all arguments together | |
ret = {} | |
for libname in libs: | |
lib_flags = kwargs(libname) | |
merge_flags(ret, lib_flags) | |
return ret | |