microproduct/atmosphericDelay/ISCEApp/site-packages/pkgconfig/pkgconfig.py

292 lines
8.8 KiB
Python

# -*- coding: utf-8 -*-
# Copyright (c) 2013 Matthias Vogelgesang <matthias.vogelgesang@gmail.com>
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
"""pkgconfig is a Python module to interface with the pkg-config command line
tool."""
import os
import shlex
import re
import collections
from functools import wraps
from subprocess import call, PIPE, Popen
class PackageNotFoundError(Exception):
"""
Raised if a package was not found.
"""
def __init__(self, package):
message = '%s not found' % package
super(PackageNotFoundError, self).__init__(message)
def _compare_versions(v1, v2):
"""
Compare two version strings and return -1, 0 or 1 depending on the equality
of the subset of matching version numbers.
The implementation is inspired by the top answer at
http://stackoverflow.com/a/1714190/997768.
"""
def normalize(v):
# strip trailing .0 or .00 or .0.0 or ...
v = re.sub(r'(\.0+)*$', '', v)
result = []
for part in v.split('.'):
# just digits
m = re.match(r'^(\d+)$', part)
if m:
result.append(int(m.group(1)))
continue
# digits letters
m = re.match(r'^(\d+)([a-zA-Z]+)$', part)
if m:
result.append(int(m.group(1)))
result.append(m.group(2))
continue
# digits letters digits
m = re.match(r'^(\d+)([a-zA-Z]+)(\d+)$', part)
if m:
result.append(int(m.group(1)))
result.append(m.group(2))
result.append(int(m.group(3)))
continue
return tuple(result)
n1 = normalize(v1)
n2 = normalize(v2)
return (n1 > n2) - (n1 < n2)
def _split_version_specifier(spec):
"""Splits version specifiers in the form ">= 0.1.2" into ('0.1.2', '>=')"""
m = re.search(r'([<>=]?=?)?\s*([0-9.a-zA-Z]+)', spec)
return m.group(2), m.group(1)
def _convert_error(func):
@wraps(func)
def _wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except OSError as e:
raise EnvironmentError("pkg-config probably not installed: %r" % e)
return _wrapper
def _build_options(option, static=False):
return (option, '--static') if static else (option,)
def _raise_if_not_exists(package):
if not exists(package):
raise PackageNotFoundError(package)
@_convert_error
def _query(package, *options):
pkg_config_exe = os.environ.get('PKG_CONFIG', None) or 'pkg-config'
cmd = '{0} {1} {2}'.format(pkg_config_exe, ' '.join(options), package)
proc = Popen(shlex.split(cmd), stdout=PIPE, stderr=PIPE)
out, err = proc.communicate()
return out.rstrip().decode('utf-8')
@_convert_error
def exists(package):
"""
Return True if package information is available.
If ``pkg-config`` not on path, raises ``EnvironmentError``.
"""
pkg_config_exe = os.environ.get('PKG_CONFIG', None) or 'pkg-config'
cmd = '{0} --exists {1}'.format(pkg_config_exe, package).split()
return call(cmd) == 0
@_convert_error
def requires(package):
"""
Return a list of package names that is required by the package.
If ``pkg-config`` not on path, raises ``EnvironmentError``.
"""
return _query(package, '--print-requires').split('\n')
def cflags(package):
"""
Return the CFLAGS string returned by pkg-config.
If ``pkg-config`` is not on path, raises ``EnvironmentError``.
"""
_raise_if_not_exists(package)
return _query(package, '--cflags')
def modversion(package):
"""
Return the version returned by pkg-config.
If `pkg-config` is not in the path, raises ``EnvironmentError``.
"""
_raise_if_not_exists(package)
return _query(package, '--modversion')
def libs(package, static=False):
"""
Return the LDFLAGS string returned by pkg-config.
The static specifier will also include libraries for static linking (i.e.,
includes any private libraries).
"""
_raise_if_not_exists(package)
return _query(package, *_build_options('--libs', static=static))
def variables(package):
"""
Return a dictionary of all the variables defined in the .pc pkg-config file
of 'package'.
"""
_raise_if_not_exists(package)
result = _query(package, '--print-variables')
names = (x.strip() for x in result.split('\n') if x != '')
return dict(((x, _query(package, '--variable={0}'.format(x)).strip()) for x in names))
def installed(package, version):
"""
Check if the package meets the required version.
The version specifier consists of an optional comparator (one of =, ==, >,
<, >=, <=) and an arbitrarily long version number separated by dots. The
should be as you would expect, e.g. for an installed version '0.1.2' of
package 'foo':
>>> installed('foo', '==0.1.2')
True
>>> installed('foo', '<0.1')
False
>>> installed('foo', '>= 0.0.4')
True
If ``pkg-config`` not on path, raises ``EnvironmentError``.
"""
if not exists(package):
return False
number, comparator = _split_version_specifier(version)
modversion = _query(package, '--modversion')
try:
result = _compare_versions(modversion, number)
except ValueError:
msg = "{0} is not a correct version specifier".format(version)
raise ValueError(msg)
if comparator in ('', '=', '=='):
return result == 0
if comparator == '>':
return result > 0
if comparator == '>=':
return result >= 0
if comparator == '<':
return result < 0
if comparator == '<=':
return result <= 0
_PARSE_MAP = {
'-D': 'define_macros',
'-I': 'include_dirs',
'-L': 'library_dirs',
'-l': 'libraries'
}
def parse(packages, static=False):
"""
Parse the output from pkg-config about the passed package or packages.
Builds a dictionary containing the 'libraries', the 'library_dirs', the
'include_dirs', and the 'define_macros' that are presented by pkg-config.
*package* is a string with space-delimited package names.
The static specifier will also include libraries for static linking (i.e.,
includes any private libraries).
If ``pkg-config`` is not on path, raises ``EnvironmentError``.
"""
for package in packages.split():
_raise_if_not_exists(package)
out = _query(packages, *_build_options('--cflags --libs', static=static))
out = out.replace('\\"', '')
result = collections.defaultdict(list)
for token in re.split(r'(?<!\\) ', out):
key = _PARSE_MAP.get(token[:2])
if key:
result[key].append(token[2:].strip())
def split(m):
t = tuple(m.split('='))
return t if len(t) > 1 else (t[0], None)
result['define_macros'] = [split(m) for m in result['define_macros']]
# only have members with values not being the empty list (which is default
# anyway):
return collections.defaultdict(list, ((k, v) for k, v in result.items() if v))
def configure_extension(ext, packages, static=False):
"""
Append the ``--cflags`` and ``--libs`` of a space-separated list of
*packages* to the ``extra_compile_args`` and ``extra_link_args`` of a
distutils/setuptools ``Extension``.
"""
for package in packages.split():
_raise_if_not_exists(package)
def query_and_extend(option, target):
os_opts = ['--msvc-syntax'] if os.name == 'nt' else []
flags = _query(packages, *os_opts, *_build_options(option, static=static))
target.extend(re.split(r'(?<!\\) ', flags.replace('\\"', '')))
query_and_extend('--cflags', ext.extra_compile_args)
query_and_extend('--libs', ext.extra_link_args)
def list_all():
"""Return a list of all packages found by pkg-config."""
packages = [line.split()[0] for line in _query('', '--list-all').split('\n')]
return packages