PageRenderTime 47ms CodeModel.GetById 7ms RepoModel.GetById 0ms app.codeStats 0ms

/numpy/distutils/npy_pkg_config.py

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