[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