pkgconfig.py 4.4 KB

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