pkgconfig-1.6.0/LICENSE0000644000000000000000000000211115152533247011422 0ustar00Copyright (c) 2013 Matthias Vogelgesang 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-1.6.0/README.rst0000644000000000000000000000751215152533247012116 0ustar00pkgconfig ========= .. image:: https://github.com/matze/pkgconfig/workflows/CI/badge.svg :target: https://github.com/matze/pkgconfig/actions/workflows/ci.yml ``pkgconfig`` is a Python module to interface with the ``pkg-config`` command-line tool for Python 3.9+. The ``pkg-config`` command-line tool typically comes from a package either called ``pkg-config`` (the original implementation) or ``pkgconf`` (a more recent, improved reimplementation intended as a drop-in replacement). The ``pkgconfig`` module can be used to - find all pkg-config packages :: >>> packages = pkgconfig.list_all() - check if a package exists :: >>> pkgconfig.exists('glib-2.0') True - check if a package meets certain version requirements :: >>> pkgconfig.installed('glib-2.0', '< 2.26') False - return the version :: >>> pkgconfig.modversion('glib-2.0') '2.56.3' - query CFLAGS and LDFLAGS :: >>> pkgconfig.cflags('glib-2.0') '-I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include' >>> pkgconfig.libs('glib-2.0') '-lglib-2.0' - get all variables defined for a package:: >>> pkgconfig.variables('glib-2.0') {u'exec_prefix': u'/usr'} - parse the output to build extensions with setup.py :: >>> d = pkgconfig.parse('glib-2.0 gtk+-2.0') >>> d['libraries'] [u'gtk+-2.0', u'glib-2.0'] or :: >>> ext = Extension('foo', ['foo.c']) >>> # sets extension attributes as needed >>> pkgconfig.configure_extension(ext, 'glib-2.0 gtk+-2.0') The ``pkgconfig.parse`` function returns a dictionary of lists. The lists returned are accurate representations of the equivalent ``pkg-config`` call's results, both in content and order. If ``pkg-config`` is not on the PATH, an ``EnvironmentError`` is raised. The ``pkgconfig`` module is licensed under the MIT license. Changelog --------- Version 1.6.0 ~~~~~~~~~~~~~ - Drop support for Python < 3.9, support 3.9 .. 3.14. - README: clarify pkg-config and pkgconf, #75 - Switch CI from Travis to GitHub Actions; test on all supported Python versions. - Use src/ layout for project, fixes #36. Also move tests and test data to tests/. - Replace deprecated distutils with setuptools to make tests work on Python 3.12; fixes #64. - Fix configure_extension for empty cflags or libs. Version 1.5.5 ~~~~~~~~~~~~~ - Make tests more robust by sorting. Version 1.5.4 ~~~~~~~~~~~~~ - Adjust pyproject.toml and drop Python 2 support Version 1.5.3 ~~~~~~~~~~~~~ - Add ``configure_extension`` API Version 1.5.2 ~~~~~~~~~~~~~ - Update poetry dependency - Improve CI Version 1.5.0 ~~~~~~~~~~~~~ - Use poetry instead of setuptools directly - Fix #42: raise exception if package is missing - Fix version parsing for openssl-like version numbers, fixes #32 - Fix #31: expose --modversion - Fix #30: strip whitespace from variable names Version 1.4.0 ~~~~~~~~~~~~~ - Add boolean ``static`` keyword to output private libraries as well - Raise original ``OSError`` as well Version 1.3.1 ~~~~~~~~~~~~~ - Fix compatibility problems with Python 2.6 Version 1.3.0 ~~~~~~~~~~~~~ - Add variables() API to query defined variables - Disable Python 3.2 and enable Python 3.5 and 3.6 tests - Fix #16: handle spaces of values in .pc files correctly Version 1.2.1 and 1.2.2 ~~~~~~~~~~~~~~~~~~~~~~~ Bugfix releases on December 1 and 2, 2016. - Include the ``data`` folder in the distribution in order to run tests - Improve the tests Version 1.2.0 ~~~~~~~~~~~~~ Released on November 30th, 2016. - Potential break: switch from result set to list - Expose the --list-all query - Added support for the PKG_CONFIG environment variable Version 1.1.0 ~~~~~~~~~~~~~ Released on November 6th, 2013. - Multiple packages can now be parsed with a single call to ``.parse``. Version 1.0.0 ~~~~~~~~~~~~~ First release on September 8th, 2013. pkgconfig-1.6.0/pyproject.toml0000644000000000000000000000167215152533247013344 0ustar00[build-system] requires = ["poetry_core>=1.0.0"] build-backend = "poetry.core.masonry.api" [tool.poetry] name = "pkgconfig" version = "1.6.0" license = "MIT" description = "Interface Python with pkg-config" authors = ["Matthias Vogelgesang "] readme = "README.rst" homepage = "https://github.com/matze/pkgconfig" classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Operating System :: OS Independent", "Topic :: Software Development :: Build Tools", ] [tool.poetry.dependencies] python = "^3.9.0" [tool.poetry.group.dev.dependencies] pytest = "^8.4.2" tox = "^4.29.0" [tool.tox] legacy_tox_ini = """ [tox] # test on all supported Python versions, see also: # - pyproject.toml: tool.poetry.dependencies # - .github/workflows/ci.yml # - README.rst envlist = py{39,310,311,312,313,314} [testenv] deps = pytest setuptools commands= pytest -v -rs """ pkgconfig-1.6.0/src/pkgconfig/__init__.py0000644000000000000000000000003115152533247015263 0ustar00from .pkgconfig import * pkgconfig-1.6.0/src/pkgconfig/pkgconfig.py0000644000000000000000000002171115152533247015503 0ustar00# -*- coding: utf-8 -*- # Copyright (c) 2013 Matthias Vogelgesang # 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`` is not on the PATH, an ``EnvironmentError`` is raised. """ 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 are required by the package. If ``pkg-config`` is not on the PATH, an ``EnvironmentError`` is raised. """ return _query(package, '--print-requires').split('\n') def cflags(package): """ Return the CFLAGS string returned by pkg-config. If ``pkg-config`` is not on the PATH, an ``EnvironmentError`` is raised. """ _raise_if_not_exists(package) return _query(package, '--cflags') def modversion(package): """ Return the version returned by pkg-config. If ``pkg-config`` is not on the PATH, an ``EnvironmentError`` is raised. """ _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 the 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 comparisons behave as you would expect. For example, 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`` is not on the PATH, an ``EnvironmentError`` is raised. """ 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. *packages* 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 the PATH, an ``EnvironmentError`` is raised. """ 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'(? 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)) flags = flags.replace('\\"', '') if flags: target.extend(re.split(r'(?=3.9.0,<4.0.0 Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: 3.13 Classifier: Programming Language :: Python :: 3.14 Classifier: Topic :: Software Development :: Build Tools Project-URL: Homepage, https://github.com/matze/pkgconfig Description-Content-Type: text/x-rst pkgconfig ========= .. image:: https://github.com/matze/pkgconfig/workflows/CI/badge.svg :target: https://github.com/matze/pkgconfig/actions/workflows/ci.yml ``pkgconfig`` is a Python module to interface with the ``pkg-config`` command-line tool for Python 3.9+. The ``pkg-config`` command-line tool typically comes from a package either called ``pkg-config`` (the original implementation) or ``pkgconf`` (a more recent, improved reimplementation intended as a drop-in replacement). The ``pkgconfig`` module can be used to - find all pkg-config packages :: >>> packages = pkgconfig.list_all() - check if a package exists :: >>> pkgconfig.exists('glib-2.0') True - check if a package meets certain version requirements :: >>> pkgconfig.installed('glib-2.0', '< 2.26') False - return the version :: >>> pkgconfig.modversion('glib-2.0') '2.56.3' - query CFLAGS and LDFLAGS :: >>> pkgconfig.cflags('glib-2.0') '-I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include' >>> pkgconfig.libs('glib-2.0') '-lglib-2.0' - get all variables defined for a package:: >>> pkgconfig.variables('glib-2.0') {u'exec_prefix': u'/usr'} - parse the output to build extensions with setup.py :: >>> d = pkgconfig.parse('glib-2.0 gtk+-2.0') >>> d['libraries'] [u'gtk+-2.0', u'glib-2.0'] or :: >>> ext = Extension('foo', ['foo.c']) >>> # sets extension attributes as needed >>> pkgconfig.configure_extension(ext, 'glib-2.0 gtk+-2.0') The ``pkgconfig.parse`` function returns a dictionary of lists. The lists returned are accurate representations of the equivalent ``pkg-config`` call's results, both in content and order. If ``pkg-config`` is not on the PATH, an ``EnvironmentError`` is raised. The ``pkgconfig`` module is licensed under the MIT license. Changelog --------- Version 1.6.0 ~~~~~~~~~~~~~ - Drop support for Python < 3.9, support 3.9 .. 3.14. - README: clarify pkg-config and pkgconf, #75 - Switch CI from Travis to GitHub Actions; test on all supported Python versions. - Use src/ layout for project, fixes #36. Also move tests and test data to tests/. - Replace deprecated distutils with setuptools to make tests work on Python 3.12; fixes #64. - Fix configure_extension for empty cflags or libs. Version 1.5.5 ~~~~~~~~~~~~~ - Make tests more robust by sorting. Version 1.5.4 ~~~~~~~~~~~~~ - Adjust pyproject.toml and drop Python 2 support Version 1.5.3 ~~~~~~~~~~~~~ - Add ``configure_extension`` API Version 1.5.2 ~~~~~~~~~~~~~ - Update poetry dependency - Improve CI Version 1.5.0 ~~~~~~~~~~~~~ - Use poetry instead of setuptools directly - Fix #42: raise exception if package is missing - Fix version parsing for openssl-like version numbers, fixes #32 - Fix #31: expose --modversion - Fix #30: strip whitespace from variable names Version 1.4.0 ~~~~~~~~~~~~~ - Add boolean ``static`` keyword to output private libraries as well - Raise original ``OSError`` as well Version 1.3.1 ~~~~~~~~~~~~~ - Fix compatibility problems with Python 2.6 Version 1.3.0 ~~~~~~~~~~~~~ - Add variables() API to query defined variables - Disable Python 3.2 and enable Python 3.5 and 3.6 tests - Fix #16: handle spaces of values in .pc files correctly Version 1.2.1 and 1.2.2 ~~~~~~~~~~~~~~~~~~~~~~~ Bugfix releases on December 1 and 2, 2016. - Include the ``data`` folder in the distribution in order to run tests - Improve the tests Version 1.2.0 ~~~~~~~~~~~~~ Released on November 30th, 2016. - Potential break: switch from result set to list - Expose the --list-all query - Added support for the PKG_CONFIG environment variable Version 1.1.0 ~~~~~~~~~~~~~ Released on November 6th, 2013. - Multiple packages can now be parsed with a single call to ``.parse``. Version 1.0.0 ~~~~~~~~~~~~~ First release on September 8th, 2013.