Here is a convenient way to create, parse, alter, and write .pc files. Just pass PkgConfig() a file-like object, or None to start fresh. Print the object to create a new .pc file.
Some people might want even more functionality, but I chose not to hinder the flexibility of the class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | """
pkg-config support
The PkgConfig class is useful by itself. It allows reading and writing
of pkg-config files.
This file, when executed, will read a .pc file and print the result of
processing. The result will be functionally equivalent, but not identical.
Re-running on its own output *should* produce identical results.
"""
from string import Template
from StringIO import StringIO
class PkgConfig:
"""
Contents of a .pc file for pkg-config.
This can be initialized with or without a .pc file.
Such a file consists of any combination of lines, where each is one of:
- comment (starting with #)
- variable definition (var = value)
- field definition (field: value)
(Allowable fields are listed in PgkConfig.fields.)
All fields and variables become attributes of this object.
Attributes can be altered, added, or removed at will.
Attributes are interpolated when accessed as dictionary keys.
When this is converted to a string, comments in the original file are
re-printed in their original order (but all at the top), then any
non-field attributes (as variables), then fields, and finally,
unrecognized attributes from the original file, if any.
@note Variables may not begin with '_'.
"""
fields = ['Name', 'Description', 'Version', 'Requires', 'Conflicts', 'Libs', 'Cflags']
own_comments = [
"# Unrecognized fields",
]
def __init__(self, FILE=None):
self.__field_map = dict()
self.__unrecognized_field_map = dict()
self.__var_map = dict()
self.__comments = []
self.__parse(FILE)
def __str__(self):
OUT = StringIO()
for comment in self.__comments:
print>>OUT, comment
print>>OUT
for key in sorted(self.__var_map):
print>>OUT, "%s=%s" %(key, self.__var_map[key])
print>>OUT
for key in PkgConfig.fields:
if key not in self.__field_map: continue
print>>OUT, "%s: %s" %(key, self.__field_map[key])
if self.__unrecognized_field_map:
print>>OUT
print>>OUT, PkgConfig.own_comments[0]
for key,val in self.__unrecognized_field_map.items():
print>>OUT, "%s: %s" %(key, val)
assert set(self.__field_map).issubset(PkgConfig.fields)
return OUT.getvalue()
def __interpolated(self, raw):
prev = None; current = raw
while prev != current:
# Interpolated repeatedly, until nothing changes.
#print "prev, current: %s, %s" %(prev, current)
prev = current
current = Template(prev).substitute(self.__var_map)
return current
def __getitem__(self, name):
"""
Return interpolated keys or variables.
>>> pc = PkgConfig()
>>> pc.WHO = 'me'
>>> pc.WHERE = 'ho${WHO}'
>>> pc.Required = '${WHO} and you'
>>> pc['WHO']
'me'
>>> pc['WHERE']
'home'
>>> pc['Required']
'me and you'
"""
if hasattr(self, name):
return self.__interpolated(getattr(self, name))
else:
raise IndexError, name
def __getattr__(self, name):
if name in PkgConfig.fields:
return self.__field_map.get(name, '')
elif name in self.__var_map:
return self.__var_map[name]
else:
raise AttributeError(name)
def __setattr__(self, name, val):
if name in PkgConfig.fields:
self.__field_map[name] = val
elif not name.startswith('_'):
self.__var_map[name] = val
else:
self.__dict__[name] = val
def __delattr__(self, name):
if name in self.__field_map:
del self.__field_map[name]
if name in self.__unrecognized_field_map:
del self.__unrecognized_field_map[name]
if name in self.__var_map:
del self.__var_map[name]
if name in self.__dict__:
del self.__dict__[name]
def __parse(self, PC):
if not PC: return
for line in PC:
line = line.strip()
if line.startswith('#'):
if line not in PkgConfig.own_comments:
self.__comments.append(line)
elif ':' in line: # exported variable
name, val = line.split(':')
val = val.strip()
if name not in PkgConfig.fields:
self.__unrecognized_field_map[name] = val
else:
self.__field_map[name] = val
elif '=' in line: # local variable
name, val = line.split('=')
self.__var_map[name] = val
|
I saw a pkg-config parser, but I need to be able to read and write .pc files. This makes it easy. You access variables and fields as attributes on a PkgConfig object. You can interpolate them by looking them up as dictionary keys. And you can create a new .pc file by converting the object to a string.
Here is a sample front-end, unit-tests, and the load() function from another recipe:
<pre> def loadPkgConfig(self, package, path=['/usr/lib/pkgconfig', '/usr/local/lib/pkgconfig']): """ Search for 'package' via 'path'. [Copied from another Cookbook recipe: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/464406 ] """ import os for path in self._paths: fn = os.path.join(path, '%s.pc' % package) if os.path.exists(fn): return PkgConfig(fn)
import unittest
sample_pc = """
Sample pkg-config file
prefix=./local
ignore=this
sysdir=${prefix} SZ=64 BIN=/usr/bin${SZ} TOOL=${BIN}/foobar
Name: Sample for nothing Description: Use for testing PkgConfig class. Version: 1.0 Requires: foo >= 4.010, bar >= 3.011 Libs: ${sysdir}/lib/libX.a ${sysdir}/lib/libY.a ${BIN}/../lib/libZ.a Cflags: -I${sysdir}/include -I${BIN}/../include Poo: should not be recognized """
class PkgConfigTests(unittest.TestCase): def setUp(self): self.__pc = PkgConfig(StringIO(sample_pc)) def test(self): self.assertEqual('${BIN}/foobar', self.__pc.TOOL) self.assertEqual('/usr/bin64/foobar', self.__pc['TOOL']) self.assertEqual('-I${sysdir}/include -I${BIN}/../include', self.__pc.Cflags) self.assertEqual('-I./local/include -I/usr/bin64/../include', self.__pc['Cflags']) self.assertRaises(AttributeError, self.__pc.__getattr__, 'Poo') self.__pc.X = 'bar${SZ}' self.assertEqual('bar${SZ}', self.__pc.X) self.assertEqual('bar64', self.__pc['X']) self.__pc.Conflicts = 'foobar <= 4.5' self.assertEqual('foobar <= 4.5', self.__pc.Conflicts) self.assertEqual('foobar <= 4.5', self.__pc['Conflicts']) self.assertTrue('Poo:' in str(self.__pc)) self.assertTrue('Conflicts:' in str(self.__pc)) self.assertTrue('X=' in str(self.__pc)) del self.__pc.X self.assertRaises(AttributeError, self.__pc.__getattr__, 'X') del self.__pc.Conflicts self.__pc.Conflicts # No AttributeError! self.assertFalse('Conflicts:' in str(self.__pc))
def _test(): import doctest print "%d failures in %d doctests." %doctest.testmod() unittest.main()
if __name__ == '__main__': # Should this be pipeable? Should the unit-tests be in a separate file? import sys try: if sys.argv[1] == '-': PC = sys.stdin else: PC = file(sys.argv[1]) except IndexError: _test() pc = PkgConfig(PC) sys.stdout.write(str(pc)) </pre>