| | import sys
|
| | import re
|
| | import os
|
| |
|
| | from configparser import RawConfigParser
|
| |
|
| | __all__ = ['FormatError', 'PkgNotFound', 'LibraryInfo', 'VariableSet',
|
| | 'read_config', 'parse_flags']
|
| |
|
| | _VAR = re.compile(r'\$\{([a-zA-Z0-9_-]+)\}')
|
| |
|
| | class FormatError(OSError):
|
| | """
|
| | Exception thrown when there is a problem parsing a configuration file.
|
| |
|
| | """
|
| | def __init__(self, msg):
|
| | self.msg = msg
|
| |
|
| | def __str__(self):
|
| | return self.msg
|
| |
|
| | class PkgNotFound(OSError):
|
| | """Exception raised when a package can not be located."""
|
| | def __init__(self, msg):
|
| | self.msg = msg
|
| |
|
| | def __str__(self):
|
| | return self.msg
|
| |
|
| | def parse_flags(line):
|
| | """
|
| | Parse a line from a config file containing compile flags.
|
| |
|
| | Parameters
|
| | ----------
|
| | line : str
|
| | A single line containing one or more compile flags.
|
| |
|
| | Returns
|
| | -------
|
| | d : dict
|
| | Dictionary of parsed flags, split into relevant categories.
|
| | These categories are the keys of `d`:
|
| |
|
| | * 'include_dirs'
|
| | * 'library_dirs'
|
| | * 'libraries'
|
| | * 'macros'
|
| | * 'ignored'
|
| |
|
| | """
|
| | d = {'include_dirs': [], 'library_dirs': [], 'libraries': [],
|
| | 'macros': [], 'ignored': []}
|
| |
|
| | flags = (' ' + line).split(' -')
|
| | for flag in flags:
|
| | flag = '-' + flag
|
| | if len(flag) > 0:
|
| | if flag.startswith('-I'):
|
| | d['include_dirs'].append(flag[2:].strip())
|
| | elif flag.startswith('-L'):
|
| | d['library_dirs'].append(flag[2:].strip())
|
| | elif flag.startswith('-l'):
|
| | d['libraries'].append(flag[2:].strip())
|
| | elif flag.startswith('-D'):
|
| | d['macros'].append(flag[2:].strip())
|
| | else:
|
| | d['ignored'].append(flag)
|
| |
|
| | return d
|
| |
|
| | def _escape_backslash(val):
|
| | return val.replace('\\', '\\\\')
|
| |
|
| | class LibraryInfo:
|
| | """
|
| | Object containing build information about a library.
|
| |
|
| | Parameters
|
| | ----------
|
| | name : str
|
| | The library name.
|
| | description : str
|
| | Description of the library.
|
| | version : str
|
| | Version string.
|
| | sections : dict
|
| | The sections of the configuration file for the library. The keys are
|
| | the section headers, the values the text under each header.
|
| | vars : class instance
|
| | A `VariableSet` instance, which contains ``(name, value)`` pairs for
|
| | variables defined in the configuration file for the library.
|
| | requires : sequence, optional
|
| | The required libraries for the library to be installed.
|
| |
|
| | Notes
|
| | -----
|
| | All input parameters (except "sections" which is a method) are available as
|
| | attributes of the same name.
|
| |
|
| | """
|
| | def __init__(self, name, description, version, sections, vars, requires=None):
|
| | self.name = name
|
| | self.description = description
|
| | if requires:
|
| | self.requires = requires
|
| | else:
|
| | self.requires = []
|
| | self.version = version
|
| | self._sections = sections
|
| | self.vars = vars
|
| |
|
| | def sections(self):
|
| | """
|
| | Return the section headers of the config file.
|
| |
|
| | Parameters
|
| | ----------
|
| | None
|
| |
|
| | Returns
|
| | -------
|
| | keys : list of str
|
| | The list of section headers.
|
| |
|
| | """
|
| | return list(self._sections.keys())
|
| |
|
| | def cflags(self, section="default"):
|
| | val = self.vars.interpolate(self._sections[section]['cflags'])
|
| | return _escape_backslash(val)
|
| |
|
| | def libs(self, section="default"):
|
| | val = self.vars.interpolate(self._sections[section]['libs'])
|
| | return _escape_backslash(val)
|
| |
|
| | def __str__(self):
|
| | m = ['Name: %s' % self.name, 'Description: %s' % self.description]
|
| | if self.requires:
|
| | m.append('Requires:')
|
| | else:
|
| | m.append('Requires: %s' % ",".join(self.requires))
|
| | m.append('Version: %s' % self.version)
|
| |
|
| | return "\n".join(m)
|
| |
|
| | class VariableSet:
|
| | """
|
| | Container object for the variables defined in a config file.
|
| |
|
| | `VariableSet` can be used as a plain dictionary, with the variable names
|
| | as keys.
|
| |
|
| | Parameters
|
| | ----------
|
| | d : dict
|
| | Dict of items in the "variables" section of the configuration file.
|
| |
|
| | """
|
| | def __init__(self, d):
|
| | self._raw_data = dict([(k, v) for k, v in d.items()])
|
| |
|
| | self._re = {}
|
| | self._re_sub = {}
|
| |
|
| | self._init_parse()
|
| |
|
| | def _init_parse(self):
|
| | for k, v in self._raw_data.items():
|
| | self._init_parse_var(k, v)
|
| |
|
| | def _init_parse_var(self, name, value):
|
| | self._re[name] = re.compile(r'\$\{%s\}' % name)
|
| | self._re_sub[name] = value
|
| |
|
| | def interpolate(self, value):
|
| |
|
| |
|
| | def _interpolate(value):
|
| | for k in self._re.keys():
|
| | value = self._re[k].sub(self._re_sub[k], value)
|
| | return value
|
| | while _VAR.search(value):
|
| | nvalue = _interpolate(value)
|
| | if nvalue == value:
|
| | break
|
| | value = nvalue
|
| |
|
| | return value
|
| |
|
| | def variables(self):
|
| | """
|
| | Return the list of variable names.
|
| |
|
| | Parameters
|
| | ----------
|
| | None
|
| |
|
| | Returns
|
| | -------
|
| | names : list of str
|
| | The names of all variables in the `VariableSet` instance.
|
| |
|
| | """
|
| | return list(self._raw_data.keys())
|
| |
|
| |
|
| | def __getitem__(self, name):
|
| | return self._raw_data[name]
|
| |
|
| | def __setitem__(self, name, value):
|
| | self._raw_data[name] = value
|
| | self._init_parse_var(name, value)
|
| |
|
| | def parse_meta(config):
|
| | if not config.has_section('meta'):
|
| | raise FormatError("No meta section found !")
|
| |
|
| | d = dict(config.items('meta'))
|
| |
|
| | for k in ['name', 'description', 'version']:
|
| | if not k in d:
|
| | raise FormatError("Option %s (section [meta]) is mandatory, "
|
| | "but not found" % k)
|
| |
|
| | if not 'requires' in d:
|
| | d['requires'] = []
|
| |
|
| | return d
|
| |
|
| | def parse_variables(config):
|
| | if not config.has_section('variables'):
|
| | raise FormatError("No variables section found !")
|
| |
|
| | d = {}
|
| |
|
| | for name, value in config.items("variables"):
|
| | d[name] = value
|
| |
|
| | return VariableSet(d)
|
| |
|
| | def parse_sections(config):
|
| | return meta_d, r
|
| |
|
| | def pkg_to_filename(pkg_name):
|
| | return "%s.ini" % pkg_name
|
| |
|
| | def parse_config(filename, dirs=None):
|
| | if dirs:
|
| | filenames = [os.path.join(d, filename) for d in dirs]
|
| | else:
|
| | filenames = [filename]
|
| |
|
| | config = RawConfigParser()
|
| |
|
| | n = config.read(filenames)
|
| | if not len(n) >= 1:
|
| | raise PkgNotFound("Could not find file(s) %s" % str(filenames))
|
| |
|
| |
|
| | meta = parse_meta(config)
|
| |
|
| | vars = {}
|
| | if config.has_section('variables'):
|
| | for name, value in config.items("variables"):
|
| | vars[name] = _escape_backslash(value)
|
| |
|
| |
|
| | secs = [s for s in config.sections() if not s in ['meta', 'variables']]
|
| | sections = {}
|
| |
|
| | requires = {}
|
| | for s in secs:
|
| | d = {}
|
| | if config.has_option(s, "requires"):
|
| | requires[s] = config.get(s, 'requires')
|
| |
|
| | for name, value in config.items(s):
|
| | d[name] = value
|
| | sections[s] = d
|
| |
|
| | return meta, vars, sections, requires
|
| |
|
| | def _read_config_imp(filenames, dirs=None):
|
| | def _read_config(f):
|
| | meta, vars, sections, reqs = parse_config(f, dirs)
|
| |
|
| | for rname, rvalue in reqs.items():
|
| | nmeta, nvars, nsections, nreqs = _read_config(pkg_to_filename(rvalue))
|
| |
|
| |
|
| | for k, v in nvars.items():
|
| | if not k in vars:
|
| | vars[k] = v
|
| |
|
| |
|
| | for oname, ovalue in nsections[rname].items():
|
| | if ovalue:
|
| | sections[rname][oname] += ' %s' % ovalue
|
| |
|
| | return meta, vars, sections, reqs
|
| |
|
| | meta, vars, sections, reqs = _read_config(filenames)
|
| |
|
| |
|
| |
|
| |
|
| | if not 'pkgdir' in vars and "pkgname" in vars:
|
| | pkgname = vars["pkgname"]
|
| | if not pkgname in sys.modules:
|
| | raise ValueError("You should import %s to get information on %s" %
|
| | (pkgname, meta["name"]))
|
| |
|
| | mod = sys.modules[pkgname]
|
| | vars["pkgdir"] = _escape_backslash(os.path.dirname(mod.__file__))
|
| |
|
| | return LibraryInfo(name=meta["name"], description=meta["description"],
|
| | version=meta["version"], sections=sections, vars=VariableSet(vars))
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | _CACHE = {}
|
| | def read_config(pkgname, dirs=None):
|
| | """
|
| | Return library info for a package from its configuration file.
|
| |
|
| | Parameters
|
| | ----------
|
| | pkgname : str
|
| | Name of the package (should match the name of the .ini file, without
|
| | the extension, e.g. foo for the file foo.ini).
|
| | dirs : sequence, optional
|
| | If given, should be a sequence of directories - usually including
|
| | the NumPy base directory - where to look for npy-pkg-config files.
|
| |
|
| | Returns
|
| | -------
|
| | pkginfo : class instance
|
| | The `LibraryInfo` instance containing the build information.
|
| |
|
| | Raises
|
| | ------
|
| | PkgNotFound
|
| | If the package is not found.
|
| |
|
| | See Also
|
| | --------
|
| | misc_util.get_info, misc_util.get_pkg_info
|
| |
|
| | Examples
|
| | --------
|
| | >>> npymath_info = np.distutils.npy_pkg_config.read_config('npymath')
|
| | >>> type(npymath_info)
|
| | <class 'numpy.distutils.npy_pkg_config.LibraryInfo'>
|
| | >>> print(npymath_info)
|
| | Name: npymath
|
| | Description: Portable, core math library implementing C99 standard
|
| | Requires:
|
| | Version: 0.1 #random
|
| |
|
| | """
|
| | try:
|
| | return _CACHE[pkgname]
|
| | except KeyError:
|
| | v = _read_config_imp(pkg_to_filename(pkgname), dirs)
|
| | _CACHE[pkgname] = v
|
| | return v
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | if __name__ == '__main__':
|
| | from optparse import OptionParser
|
| | import glob
|
| |
|
| | parser = OptionParser()
|
| | parser.add_option("--cflags", dest="cflags", action="store_true",
|
| | help="output all preprocessor and compiler flags")
|
| | parser.add_option("--libs", dest="libs", action="store_true",
|
| | help="output all linker flags")
|
| | parser.add_option("--use-section", dest="section",
|
| | help="use this section instead of default for options")
|
| | parser.add_option("--version", dest="version", action="store_true",
|
| | help="output version")
|
| | parser.add_option("--atleast-version", dest="min_version",
|
| | help="Minimal version")
|
| | parser.add_option("--list-all", dest="list_all", action="store_true",
|
| | help="Minimal version")
|
| | parser.add_option("--define-variable", dest="define_variable",
|
| | help="Replace variable with the given value")
|
| |
|
| | (options, args) = parser.parse_args(sys.argv)
|
| |
|
| | if len(args) < 2:
|
| | raise ValueError("Expect package name on the command line:")
|
| |
|
| | if options.list_all:
|
| | files = glob.glob("*.ini")
|
| | for f in files:
|
| | info = read_config(f)
|
| | print("%s\t%s - %s" % (info.name, info.name, info.description))
|
| |
|
| | pkg_name = args[1]
|
| | d = os.environ.get('NPY_PKG_CONFIG_PATH')
|
| | if d:
|
| | info = read_config(
|
| | pkg_name, ['numpy/_core/lib/npy-pkg-config', '.', d]
|
| | )
|
| | else:
|
| | info = read_config(
|
| | pkg_name, ['numpy/_core/lib/npy-pkg-config', '.']
|
| | )
|
| |
|
| | if options.section:
|
| | section = options.section
|
| | else:
|
| | section = "default"
|
| |
|
| | if options.define_variable:
|
| | m = re.search(r'([\S]+)=([\S]+)', options.define_variable)
|
| | if not m:
|
| | raise ValueError("--define-variable option should be of "
|
| | "the form --define-variable=foo=bar")
|
| | else:
|
| | name = m.group(1)
|
| | value = m.group(2)
|
| | info.vars[name] = value
|
| |
|
| | if options.cflags:
|
| | print(info.cflags(section))
|
| | if options.libs:
|
| | print(info.libs(section))
|
| | if options.version:
|
| | print(info.version)
|
| | if options.min_version:
|
| | print(info.version >= options.min_version)
|
| |
|