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