codekingpro/portable-devtools
114k
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 