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