• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# pkg-config, https://www.freedesktop.org/wiki/Software/pkg-config/ integration for cffi
2import sys, os, subprocess
3
4from .error import PkgConfigError
5
6
7def merge_flags(cfg1, cfg2):
8    """Merge values from cffi config flags cfg2 to cf1
9
10    Example:
11        merge_flags({"libraries": ["one"]}, {"libraries": ["two"]})
12        {"libraries": ["one", "two"]}
13    """
14    for key, value in cfg2.items():
15        if key not in cfg1:
16            cfg1[key] = value
17        else:
18            if not isinstance(cfg1[key], list):
19                raise TypeError("cfg1[%r] should be a list of strings" % (key,))
20            if not isinstance(value, list):
21                raise TypeError("cfg2[%r] should be a list of strings" % (key,))
22            cfg1[key].extend(value)
23    return cfg1
24
25
26def call(libname, flag, encoding=sys.getfilesystemencoding()):
27    """Calls pkg-config and returns the output if found
28    """
29    a = ["pkg-config", "--print-errors"]
30    a.append(flag)
31    a.append(libname)
32    try:
33        pc = subprocess.Popen(a, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
34    except EnvironmentError as e:
35        raise PkgConfigError("cannot run pkg-config: %s" % (str(e).strip(),))
36
37    bout, berr = pc.communicate()
38    if pc.returncode != 0:
39        try:
40            berr = berr.decode(encoding)
41        except Exception:
42            pass
43        raise PkgConfigError(berr.strip())
44
45    if sys.version_info >= (3,) and not isinstance(bout, str):   # Python 3.x
46        try:
47            bout = bout.decode(encoding)
48        except UnicodeDecodeError:
49            raise PkgConfigError("pkg-config %s %s returned bytes that cannot "
50                                 "be decoded with encoding %r:\n%r" %
51                                 (flag, libname, encoding, bout))
52
53    if os.altsep != '\\' and '\\' in bout:
54        raise PkgConfigError("pkg-config %s %s returned an unsupported "
55                             "backslash-escaped output:\n%r" %
56                             (flag, libname, bout))
57    return bout
58
59
60def flags_from_pkgconfig(libs):
61    r"""Return compiler line flags for FFI.set_source based on pkg-config output
62
63    Usage
64        ...
65        ffibuilder.set_source("_foo", pkgconfig = ["libfoo", "libbar >= 1.8.3"])
66
67    If pkg-config is installed on build machine, then arguments include_dirs,
68    library_dirs, libraries, define_macros, extra_compile_args and
69    extra_link_args are extended with an output of pkg-config for libfoo and
70    libbar.
71
72    Raises PkgConfigError in case the pkg-config call fails.
73    """
74
75    def get_include_dirs(string):
76        return [x[2:] for x in string.split() if x.startswith("-I")]
77
78    def get_library_dirs(string):
79        return [x[2:] for x in string.split() if x.startswith("-L")]
80
81    def get_libraries(string):
82        return [x[2:] for x in string.split() if x.startswith("-l")]
83
84    # convert -Dfoo=bar to list of tuples [("foo", "bar")] expected by distutils
85    def get_macros(string):
86        def _macro(x):
87            x = x[2:]    # drop "-D"
88            if '=' in x:
89                return tuple(x.split("=", 1))  # "-Dfoo=bar" => ("foo", "bar")
90            else:
91                return (x, None)               # "-Dfoo" => ("foo", None)
92        return [_macro(x) for x in string.split() if x.startswith("-D")]
93
94    def get_other_cflags(string):
95        return [x for x in string.split() if not x.startswith("-I") and
96                                             not x.startswith("-D")]
97
98    def get_other_libs(string):
99        return [x for x in string.split() if not x.startswith("-L") and
100                                             not x.startswith("-l")]
101
102    # return kwargs for given libname
103    def kwargs(libname):
104        fse = sys.getfilesystemencoding()
105        all_cflags = call(libname, "--cflags")
106        all_libs = call(libname, "--libs")
107        return {
108            "include_dirs": get_include_dirs(all_cflags),
109            "library_dirs": get_library_dirs(all_libs),
110            "libraries": get_libraries(all_libs),
111            "define_macros": get_macros(all_cflags),
112            "extra_compile_args": get_other_cflags(all_cflags),
113            "extra_link_args": get_other_libs(all_libs),
114            }
115
116    # merge all arguments together
117    ret = {}
118    for libname in libs:
119        lib_flags = kwargs(libname)
120        merge_flags(ret, lib_flags)
121    return ret
122