npy_pkg_config.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. from __future__ import division, absolute_import, print_function
  2. import sys
  3. import re
  4. import os
  5. if sys.version_info[0] < 3:
  6. from ConfigParser import RawConfigParser
  7. else:
  8. from configparser import RawConfigParser
  9. __all__ = ['FormatError', 'PkgNotFound', 'LibraryInfo', 'VariableSet',
  10. 'read_config', 'parse_flags']
  11. _VAR = re.compile(r'\$\{([a-zA-Z0-9_-]+)\}')
  12. class FormatError(IOError):
  13. """
  14. Exception thrown when there is a problem parsing a configuration file.
  15. """
  16. def __init__(self, msg):
  17. self.msg = msg
  18. def __str__(self):
  19. return self.msg
  20. class PkgNotFound(IOError):
  21. """Exception raised when a package can not be located."""
  22. def __init__(self, msg):
  23. self.msg = msg
  24. def __str__(self):
  25. return self.msg
  26. def parse_flags(line):
  27. """
  28. Parse a line from a config file containing compile flags.
  29. Parameters
  30. ----------
  31. line : str
  32. A single line containing one or more compile flags.
  33. Returns
  34. -------
  35. d : dict
  36. Dictionary of parsed flags, split into relevant categories.
  37. These categories are the keys of `d`:
  38. * 'include_dirs'
  39. * 'library_dirs'
  40. * 'libraries'
  41. * 'macros'
  42. * 'ignored'
  43. """
  44. d = {'include_dirs': [], 'library_dirs': [], 'libraries': [],
  45. 'macros': [], 'ignored': []}
  46. flags = (' ' + line).split(' -')
  47. for flag in flags:
  48. flag = '-' + flag
  49. if len(flag) > 0:
  50. if flag.startswith('-I'):
  51. d['include_dirs'].append(flag[2:].strip())
  52. elif flag.startswith('-L'):
  53. d['library_dirs'].append(flag[2:].strip())
  54. elif flag.startswith('-l'):
  55. d['libraries'].append(flag[2:].strip())
  56. elif flag.startswith('-D'):
  57. d['macros'].append(flag[2:].strip())
  58. else:
  59. d['ignored'].append(flag)
  60. return d
  61. def _escape_backslash(val):
  62. return val.replace('\\', '\\\\')
  63. class LibraryInfo(object):
  64. """
  65. Object containing build information about a library.
  66. Parameters
  67. ----------
  68. name : str
  69. The library name.
  70. description : str
  71. Description of the library.
  72. version : str
  73. Version string.
  74. sections : dict
  75. The sections of the configuration file for the library. The keys are
  76. the section headers, the values the text under each header.
  77. vars : class instance
  78. A `VariableSet` instance, which contains ``(name, value)`` pairs for
  79. variables defined in the configuration file for the library.
  80. requires : sequence, optional
  81. The required libraries for the library to be installed.
  82. Notes
  83. -----
  84. All input parameters (except "sections" which is a method) are available as
  85. attributes of the same name.
  86. """
  87. def __init__(self, name, description, version, sections, vars, requires=None):
  88. self.name = name
  89. self.description = description
  90. if requires:
  91. self.requires = requires
  92. else:
  93. self.requires = []
  94. self.version = version
  95. self._sections = sections
  96. self.vars = vars
  97. def sections(self):
  98. """
  99. Return the section headers of the config file.
  100. Parameters
  101. ----------
  102. None
  103. Returns
  104. -------
  105. keys : list of str
  106. The list of section headers.
  107. """
  108. return list(self._sections.keys())
  109. def cflags(self, section="default"):
  110. val = self.vars.interpolate(self._sections[section]['cflags'])
  111. return _escape_backslash(val)
  112. def libs(self, section="default"):
  113. val = self.vars.interpolate(self._sections[section]['libs'])
  114. return _escape_backslash(val)
  115. def __str__(self):
  116. m = ['Name: %s' % self.name, 'Description: %s' % self.description]
  117. if self.requires:
  118. m.append('Requires:')
  119. else:
  120. m.append('Requires: %s' % ",".join(self.requires))
  121. m.append('Version: %s' % self.version)
  122. return "\n".join(m)
  123. class VariableSet(object):
  124. """
  125. Container object for the variables defined in a config file.
  126. `VariableSet` can be used as a plain dictionary, with the variable names
  127. as keys.
  128. Parameters
  129. ----------
  130. d : dict
  131. Dict of items in the "variables" section of the configuration file.
  132. """
  133. def __init__(self, d):
  134. self._raw_data = dict([(k, v) for k, v in d.items()])
  135. self._re = {}
  136. self._re_sub = {}
  137. self._init_parse()
  138. def _init_parse(self):
  139. for k, v in self._raw_data.items():
  140. self._init_parse_var(k, v)
  141. def _init_parse_var(self, name, value):
  142. self._re[name] = re.compile(r'\$\{%s\}' % name)
  143. self._re_sub[name] = value
  144. def interpolate(self, value):
  145. # Brute force: we keep interpolating until there is no '${var}' anymore
  146. # or until interpolated string is equal to input string
  147. def _interpolate(value):
  148. for k in self._re.keys():
  149. value = self._re[k].sub(self._re_sub[k], value)
  150. return value
  151. while _VAR.search(value):
  152. nvalue = _interpolate(value)
  153. if nvalue == value:
  154. break
  155. value = nvalue
  156. return value
  157. def variables(self):
  158. """
  159. Return the list of variable names.
  160. Parameters
  161. ----------
  162. None
  163. Returns
  164. -------
  165. names : list of str
  166. The names of all variables in the `VariableSet` instance.
  167. """
  168. return list(self._raw_data.keys())
  169. # Emulate a dict to set/get variables values
  170. def __getitem__(self, name):
  171. return self._raw_data[name]
  172. def __setitem__(self, name, value):
  173. self._raw_data[name] = value
  174. self._init_parse_var(name, value)
  175. def parse_meta(config):
  176. if not config.has_section('meta'):
  177. raise FormatError("No meta section found !")
  178. d = dict(config.items('meta'))
  179. for k in ['name', 'description', 'version']:
  180. if not k in d:
  181. raise FormatError("Option %s (section [meta]) is mandatory, "
  182. "but not found" % k)
  183. if not 'requires' in d:
  184. d['requires'] = []
  185. return d
  186. def parse_variables(config):
  187. if not config.has_section('variables'):
  188. raise FormatError("No variables section found !")
  189. d = {}
  190. for name, value in config.items("variables"):
  191. d[name] = value
  192. return VariableSet(d)
  193. def parse_sections(config):
  194. return meta_d, r
  195. def pkg_to_filename(pkg_name):
  196. return "%s.ini" % pkg_name
  197. def parse_config(filename, dirs=None):
  198. if dirs:
  199. filenames = [os.path.join(d, filename) for d in dirs]
  200. else:
  201. filenames = [filename]
  202. config = RawConfigParser()
  203. n = config.read(filenames)
  204. if not len(n) >= 1:
  205. raise PkgNotFound("Could not find file(s) %s" % str(filenames))
  206. # Parse meta and variables sections
  207. meta = parse_meta(config)
  208. vars = {}
  209. if config.has_section('variables'):
  210. for name, value in config.items("variables"):
  211. vars[name] = _escape_backslash(value)
  212. # Parse "normal" sections
  213. secs = [s for s in config.sections() if not s in ['meta', 'variables']]
  214. sections = {}
  215. requires = {}
  216. for s in secs:
  217. d = {}
  218. if config.has_option(s, "requires"):
  219. requires[s] = config.get(s, 'requires')
  220. for name, value in config.items(s):
  221. d[name] = value
  222. sections[s] = d
  223. return meta, vars, sections, requires
  224. def _read_config_imp(filenames, dirs=None):
  225. def _read_config(f):
  226. meta, vars, sections, reqs = parse_config(f, dirs)
  227. # recursively add sections and variables of required libraries
  228. for rname, rvalue in reqs.items():
  229. nmeta, nvars, nsections, nreqs = _read_config(pkg_to_filename(rvalue))
  230. # Update var dict for variables not in 'top' config file
  231. for k, v in nvars.items():
  232. if not k in vars:
  233. vars[k] = v
  234. # Update sec dict
  235. for oname, ovalue in nsections[rname].items():
  236. if ovalue:
  237. sections[rname][oname] += ' %s' % ovalue
  238. return meta, vars, sections, reqs
  239. meta, vars, sections, reqs = _read_config(filenames)
  240. # FIXME: document this. If pkgname is defined in the variables section, and
  241. # there is no pkgdir variable defined, pkgdir is automatically defined to
  242. # the path of pkgname. This requires the package to be imported to work
  243. if not 'pkgdir' in vars and "pkgname" in vars:
  244. pkgname = vars["pkgname"]
  245. if not pkgname in sys.modules:
  246. raise ValueError("You should import %s to get information on %s" %
  247. (pkgname, meta["name"]))
  248. mod = sys.modules[pkgname]
  249. vars["pkgdir"] = _escape_backslash(os.path.dirname(mod.__file__))
  250. return LibraryInfo(name=meta["name"], description=meta["description"],
  251. version=meta["version"], sections=sections, vars=VariableSet(vars))
  252. # Trivial cache to cache LibraryInfo instances creation. To be really
  253. # efficient, the cache should be handled in read_config, since a same file can
  254. # be parsed many time outside LibraryInfo creation, but I doubt this will be a
  255. # problem in practice
  256. _CACHE = {}
  257. def read_config(pkgname, dirs=None):
  258. """
  259. Return library info for a package from its configuration file.
  260. Parameters
  261. ----------
  262. pkgname : str
  263. Name of the package (should match the name of the .ini file, without
  264. the extension, e.g. foo for the file foo.ini).
  265. dirs : sequence, optional
  266. If given, should be a sequence of directories - usually including
  267. the NumPy base directory - where to look for npy-pkg-config files.
  268. Returns
  269. -------
  270. pkginfo : class instance
  271. The `LibraryInfo` instance containing the build information.
  272. Raises
  273. ------
  274. PkgNotFound
  275. If the package is not found.
  276. See Also
  277. --------
  278. misc_util.get_info, misc_util.get_pkg_info
  279. Examples
  280. --------
  281. >>> npymath_info = np.distutils.npy_pkg_config.read_config('npymath')
  282. >>> type(npymath_info)
  283. <class 'numpy.distutils.npy_pkg_config.LibraryInfo'>
  284. >>> print(npymath_info)
  285. Name: npymath
  286. Description: Portable, core math library implementing C99 standard
  287. Requires:
  288. Version: 0.1 #random
  289. """
  290. try:
  291. return _CACHE[pkgname]
  292. except KeyError:
  293. v = _read_config_imp(pkg_to_filename(pkgname), dirs)
  294. _CACHE[pkgname] = v
  295. return v
  296. # TODO:
  297. # - implements version comparison (modversion + atleast)
  298. # pkg-config simple emulator - useful for debugging, and maybe later to query
  299. # the system
  300. if __name__ == '__main__':
  301. import sys
  302. from optparse import OptionParser
  303. import glob
  304. parser = OptionParser()
  305. parser.add_option("--cflags", dest="cflags", action="store_true",
  306. help="output all preprocessor and compiler flags")
  307. parser.add_option("--libs", dest="libs", action="store_true",
  308. help="output all linker flags")
  309. parser.add_option("--use-section", dest="section",
  310. help="use this section instead of default for options")
  311. parser.add_option("--version", dest="version", action="store_true",
  312. help="output version")
  313. parser.add_option("--atleast-version", dest="min_version",
  314. help="Minimal version")
  315. parser.add_option("--list-all", dest="list_all", action="store_true",
  316. help="Minimal version")
  317. parser.add_option("--define-variable", dest="define_variable",
  318. help="Replace variable with the given value")
  319. (options, args) = parser.parse_args(sys.argv)
  320. if len(args) < 2:
  321. raise ValueError("Expect package name on the command line:")
  322. if options.list_all:
  323. files = glob.glob("*.ini")
  324. for f in files:
  325. info = read_config(f)
  326. print("%s\t%s - %s" % (info.name, info.name, info.description))
  327. pkg_name = args[1]
  328. d = os.environ.get('NPY_PKG_CONFIG_PATH')
  329. if d:
  330. info = read_config(pkg_name, ['numpy/core/lib/npy-pkg-config', '.', d])
  331. else:
  332. info = read_config(pkg_name, ['numpy/core/lib/npy-pkg-config', '.'])
  333. if options.section:
  334. section = options.section
  335. else:
  336. section = "default"
  337. if options.define_variable:
  338. m = re.search(r'([\S]+)=([\S]+)', options.define_variable)
  339. if not m:
  340. raise ValueError("--define-variable option should be of "
  341. "the form --define-variable=foo=bar")
  342. else:
  343. name = m.group(1)
  344. value = m.group(2)
  345. info.vars[name] = value
  346. if options.cflags:
  347. print(info.cflags(section))
  348. if options.libs:
  349. print(info.libs(section))
  350. if options.version:
  351. print(info.version)
  352. if options.min_version:
  353. print(info.version >= options.min_version)