Welcome, guest | Sign In | My Account | Store | Cart

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.

Python, 128 lines
  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>