[pypy-commit] cffi default: Merged in vyskocilm/cffi (pull request #80)
arigo
pypy.commits at gmail.com
Thu Jan 31 04:26:37 EST 2019
Author: Armin Rigo <armin.rigo at gmail.com>
Branch:
Changeset: r3204:b21780fe49f6
Date: 2019-01-31 09:26 +0000
http://bitbucket.org/cffi/cffi/changeset/b21780fe49f6/
Log: Merged in vyskocilm/cffi (pull request #80)
Passing of proper CFLAGS/CXXFLAGS/LDFLAGS is hard and error prone
diff --git a/cffi/api.py b/cffi/api.py
--- a/cffi/api.py
+++ b/cffi/api.py
@@ -2,6 +2,7 @@
from .lock import allocate_lock
from .error import CDefError
from . import model
+from . import pkgconfig
try:
callable
@@ -640,6 +641,9 @@
if os.sep in module_name or (os.altsep and os.altsep in module_name):
raise ValueError("'module_name' must not contain '/': use a dotted "
"name to make a 'package.module' location")
+ if "pkgconfig" in kwds:
+ pkgconfig.merge_flags(kwds, pkgconfig.flags(kwds["pkgconfig"]))
+ del kwds["pkgconfig"]
self._assigned_source = (str(module_name), source,
source_extension, kwds)
diff --git a/cffi/error.py b/cffi/error.py
--- a/cffi/error.py
+++ b/cffi/error.py
@@ -21,3 +21,10 @@
""" An error raised when incomplete structures are passed into
cdef, but no verification has been done
"""
+
+class PkgConfigError(Exception):
+ """ An error raised for all pkg-config related errors
+ except version mismatch"""
+
+class PkgConfigModuleVersionNotFound(Exception):
+ """ An error raised when requested version was not found"""
diff --git a/cffi/pkgconfig.py b/cffi/pkgconfig.py
new file mode 100644
--- /dev/null
+++ b/cffi/pkgconfig.py
@@ -0,0 +1,105 @@
+# pkg-config, https://www.freedesktop.org/wiki/Software/pkg-config/ integration for cffi
+import subprocess
+import sys
+import re
+
+from .error import PkgConfigModuleVersionNotFound
+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 not key in cfg1:
+ cfg1 [key] = value
+ else:
+ cfg1 [key].extend(value)
+ return cfg1
+
+
+def call(libname, flag):
+ """Calls pkg-config and returing the output if found
+ """
+ a = ["pkg-config", "--print-errors"]
+ a.append(flag)
+ a.append(libname)
+ pc = None
+ try:
+ pc = subprocess.Popen(a, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ except FileNotFoundError:
+ pass
+ if pc is None:
+ raise PkgConfigError("pkg-config was not found on this system")
+
+ bout, berr = pc.communicate()
+ if berr is not None:
+ err = berr.decode(sys.getfilesystemencoding())
+ if re.search("Requested '.*' but version of ", err, re.MULTILINE) is not None:
+ raise PkgConfigModuleVersionNotFound(err)
+ else:
+ PkgConfigError(err)
+ return bout
+
+
+def flags(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
+ * PkgConfigModuleVersionNotFound if requested version does not match
+ * PkgConfigError for all other errors
+ """
+
+ subprocess.check_output(["pkg-config", "--version"])
+
+ # make API great again!
+ if isinstance(libs, (str, bytes)):
+ libs = (libs, )
+
+ # drop starting -I -L -l from cflags
+ def dropILl(string):
+ def _dropILl(string):
+ if string.startswith("-I") or string.startswith("-L") or string.startswith("-l"):
+ return string [2:]
+ return [_dropILl(x) for x in string.split()]
+
+ # convert -Dfoo=bar to list of tuples [("foo", "bar")] expected by cffi
+ def macros(string):
+ def _macros(string):
+ return tuple(string [2:].split("=", 2))
+ return [_macros(x) for x in string.split() if x.startswith("-D")]
+
+ def drop_macros(string):
+ return [x for x in string.split() if not x.startswith("-D")]
+
+ # return kwargs for given libname
+ def kwargs(libname):
+ fse = sys.getfilesystemencoding()
+ return {
+ "include_dirs" : dropILl(call(libname, "--cflags-only-I").decode(fse)),
+ "library_dirs" : dropILl(call(libname, "--libs-only-L").decode(fse)),
+ "libraries" : dropILl(call(libname, "--libs-only-l").decode(fse)),
+ "define_macros" : macros(call(libname, "--cflags-only-other").decode('ascii')),
+ "extra_compile_args" : drop_macros(call(libname, "--cflags-only-other").decode('ascii')),
+ "extra_link_args" : call(libname, "--libs-only-other").decode('ascii').split()
+ }
+
+ # merge all arguments together
+ ret = {}
+ for libname in libs:
+ foo = kwargs(libname)
+ merge_flags(ret, foo)
+
+ return ret
diff --git a/testing/cffi1/test_pkgconfig.py b/testing/cffi1/test_pkgconfig.py
new file mode 100644
--- /dev/null
+++ b/testing/cffi1/test_pkgconfig.py
@@ -0,0 +1,43 @@
+import sys
+import subprocess
+import py
+import cffi.pkgconfig as pkgconfig
+
+def mock_call(libname, flag):
+ assert libname=="python-3.6", "mocked pc function supports python-3.6 input ONLY"
+
+ flags = {
+ "--cflags-only-I": b"-I/usr/include/python3.6m\n",
+ "--libs-only-L": b"-L/usr/lib64\n",
+ "--libs-only-l": b"-lpython3.6\n",
+ "--cflags-only-other": b"-DCFFI_TEST=1 -O42\n",
+ "--libs-only-other": b"-lm\n",
+ }
+ return flags[flag]
+
+pkgconfig.call = mock_call
+
+
+def test_merge_flags():
+
+ d1 = {"ham": [1, 2, 3], "spam" : ["a", "b", "c"], "foo" : []}
+ d2 = {"spam" : ["spam", "spam", "spam"], "bar" : ["b", "a", "z"]}
+
+ pkgconfig.merge_flags(d1, d2)
+ assert d1 == {
+ "ham": [1, 2, 3],
+ "spam" : ["a", "b", "c", "spam", "spam", "spam"],
+ "bar" : ["b", "a", "z"],
+ "foo" : []}
+
+
+def test_pkgconfig():
+ flags = pkgconfig.flags("python-3.6")
+ assert flags == {
+ 'include_dirs': [u'/usr/include/python3.6m'],
+ 'library_dirs': [u'/usr/lib64'],
+ 'libraries': [u'python3.6'],
+ 'define_macros': [(u'CFFI_TEST', u'1')],
+ 'extra_compile_args': [u'-O42'],
+ 'extra_link_args': [u'-lm']
+ }
More information about the pypy-commit
mailing list