source: rtems-source-builder/source-builder/sb/pkgconfig.py @ ee93200

4.104.114.95
Last change on this file since ee93200 was c4fefde, checked in by Chris Johns <chrisj@…>, on 02/10/14 at 23:18:35

sb: Add pkg-config support.

Add a pkg-config look alike command so packages that use pkg-config can
build if pkg-config is not present on a host.

Add support to query package config from configuration scripts.

  • Property mode set to 100755
File size: 16.6 KB
Line 
1#! /usr/bin/env python
2#
3# RTEMS Tools Project (http://www.rtems.org/)
4# Copyright 2014 Chris Johns (chrisj@rtems.org)
5# All rights reserved.
6#
7# This file is part of the RTEMS Tools package in 'rtems-tools'.
8#
9# Redistribution and use in source and binary forms, with or without
10# modification, are permitted provided that the following conditions are met:
11#
12# 1. Redistributions of source code must retain the above copyright notice,
13# this list of conditions and the following disclaimer.
14#
15# 2. Redistributions in binary form must reproduce the above copyright notice,
16# this list of conditions and the following disclaimer in the documentation
17# and/or other materials provided with the distribution.
18#
19# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29# POSSIBILITY OF SUCH DAMAGE.
30#
31
32#
33# Pkg-config in python. It attempts to provide a few simple features
34# provided by the full pkg-config so packages can configure and build.
35#
36
37import argparse
38import os
39import os.path
40import re
41import shlex
42import sys
43
44def default_prefix():
45    paths = ['/usr', '/usr/local']
46    if 'PKG_CONFIG_PATH' in os.environ:
47        paths += os.environ['PKG_CONFIG_PATH'].split(':')
48    return paths
49
50class error(Exception):
51    def __init__(self, msg):
52        self.msg = msg
53
54    def __str__(self):
55        return self.msg
56
57class package(object):
58
59    node_types = ['requires', 'requires.private']
60    node_type_labels = { 'requires': 'r', 'requires.private': 'rp', 'failed': 'F' }
61    version_ops = ['=', '<', '>', '<=', '>=', '!=']
62    config_prefixes = ['lib', 'libdata']
63    get_recursion = ['cflags', 'libs']
64    no_dup_flags = ['-I', '-l', '-L']
65    dual_opts = ['-D', '-U', '-I', '-l', '-L']
66    lib_list_splitter = re.compile('[\s,]+')
67    loaded = {}
68
69    @staticmethod
70    def is_version(v):
71        for n in v.split('.'):
72            if not n.isdigit():
73                return False
74        return True
75
76    @staticmethod
77    def splitter(pkg_list):
78        pkgs = []
79        pls = package.lib_list_splitter.split(pkg_list)
80        i = 0
81        while i < len(pls):
82            pkg = [pls[i]]
83            i += 1
84            if i < len(pls):
85                op = None
86                if package.is_version(pls[i]):
87                    op = '>='
88                    ver = pls[i]
89                    i += 1
90                elif pls[i] in package.version_ops:
91                    op = pls[i]
92                    i += 1
93                    if i < len(pls):
94                        ver = pls[i]
95                        i += 1
96                else:
97                    op = '>='
98                    ver = '0'
99                pkg += [op, ver]
100            else:
101                pkg += ['>=', '0']
102            pkgs += [pkg]
103        return pkgs
104
105    @staticmethod
106    def check_versions(lhs, op, rhs):
107        if op not in package.version_ops:
108            raise error('bad operator: %s' % (op))
109        if not lhs or not rhs:
110            return False
111        slhs = lhs.split('.')
112        srhs = rhs.split('.')
113        ok = True
114        i = 0
115        while i < len(srhs):
116            try:
117                l = int(slhs[i])
118                r = int(srhs[i])
119            except:
120                return False
121            if op == '=':
122                if l != r:
123                    ok = False
124                    break
125            elif op == '<':
126                if l < r:
127                    break
128                if l >= r:
129                    ok = False
130                    break
131            elif op == '>':
132                if l > r:
133                    break
134                if l <= r:
135                    ok = False
136                    break
137            elif op == '<=':
138                if l < r:
139                    break
140                if l > r:
141                    ok = False
142                    break
143            elif op == '>=':
144                if l > r:
145                    break
146                if l < r:
147                    ok = False
148                    break
149            elif op == '!=':
150                if l != r:
151                    ok = True
152                    break
153                if l == r:
154                    ok = False
155            i += 1
156        return ok
157
158    @staticmethod
159    def dump_loaded():
160        for n in sorted(package.loaded):
161            print package.loaded[n]._str()
162
163    def __init__(self, name = None, prefix = None, output = None, src = None):
164        self._clean()
165        self.name_ = name
166        self.output = output
167        self.src = src
168        self.prefix = None
169        self.paths = []
170        if prefix is None:
171            prefix = default_prefix()
172        if prefix:
173            if type(prefix) is str:
174                self.prefix = prefix.split(os.pathsep)
175            elif type(prefix) is list:
176                self.prefix = prefix
177            else:
178                raise error('invalid type of prefix')
179            for p in self.prefix:
180                for d in package.config_prefixes:
181                    prefix = os.path.join(p, d, 'pkgconfig')
182                    if os.path.exists(prefix):
183                        self.paths += [prefix]
184            self._log('paths: %s' % (', '.join(self.paths)))
185        if 'PKG_CONFIG_SYSROOT_DIR' in os.environ:
186            self.defines['sysroot'] = os.environ['PKG_CONFIG_SYSROOT_DIR']
187            self._log('sysroot: %s' % (self.defines['sysroot']))
188        if 'PKG_CONFIG_BUILD_TOP_DIR' in os.environ:
189            self.defines['top_builddir'] = os.environ['PKG_CONFIG_BUILD_TOP_DIR']
190            self._log('top_builddir: %s' % (self.defines['top_builddir']))
191        if self.name_:
192            self.load(self.name_)
193
194    def __str__(self):
195        s = self._str()
196        for nt in package.node_types:
197            for n in sorted(self.nodes[nt]):
198                s += ' ' + \
199                     ' '.join(['%s%s' % (s, os.linesep) \
200                               for s in str(self.nodes[nt][n]).split(os.linesep)])
201        return s[:-1]
202
203    def _str(self):
204        if self.file_ or len(self.libraries):
205            e = 'E'
206        else:
207            e = '-'
208        s = '> %s %s (%s)%s' % (self.name_, e, self.file_, os.linesep)
209        for d in sorted(self.defines):
210            s += 'd: %s: %s%s' % (d, self.defines[d], os.linesep)
211        for f in sorted(self.fields):
212            s += 'f: %s: %s%s' % (f, self.fields[f], os.linesep)
213        for l in sorted(self.libraries):
214            s += 'l: %s%s' % (l, os.linesep)
215        for nt in package.node_types + ['failed']:
216            s += '%s: ' % (package.node_type_labels[nt])
217            if len(self.nodes[nt]):
218                txt = []
219                for n in sorted(self.nodes[nt]):
220                    if self.nodes[nt][n].exists():
221                        e = ''
222                    else:
223                        e = '*'
224                    txt += ['%s%s' % (n, e)]
225                s += '%s%s' % (', '.join(txt), os.linesep)
226            else:
227                s += 'none' + os.linesep
228        return s #[:-1]
229
230    def _clean(self):
231        self.name_ = None
232        self.file_ = None
233        self.defines = {}
234        self.fields = {}
235        self.nodes = { 'failed': {} }
236        for nt in package.node_types:
237            self.nodes[nt] = {}
238        self.libraries = []
239
240    def _log(self, s):
241        if self.output:
242            self.output(s)
243
244    def _find_package(self, name):
245        if len(self.paths):
246            for path in self.paths:
247                pc = os.path.join(path, '%s.pc' % (name))
248                if os.path.isfile(pc):
249                    return pc;
250        return None
251
252    def _find_libraries(self, name):
253        libraries = []
254        for prefix in self.prefix:
255                prefix = os.path.join(prefix, 'lib')
256                if os.path.exists(prefix):
257                    for l in os.listdir(prefix):
258                        if l.startswith(name + '.'):
259                            libraries += [os.path.join(prefix, l)]
260                            break
261        return libraries
262
263    def _filter_sysroot(self, s):
264        if 'sysroot' in self.defines:
265            sysroot = self.defines['sysroot']
266            offset = 0
267            while True:
268                dash = s[offset:].find('-')
269                if dash < 0:
270                    break
271                if offset + dash + 2 < len(s) and s[offset + dash + 1] in 'LI':
272                    s = s[:offset + dash + 2] + sysroot + s[offset + dash + 2:]
273                offset += dash + 1
274        return s
275
276    def _filter_top_builddir(self, s):
277        if 'top_builddir' in self.defines:
278            top_builddir = self.defines['top_builddir']
279            if self.file.startswith(top_builddir) and not s.startswith(top_builddir):
280                offset = 0
281                while True:
282                    dash = s[offset:].find('-')
283                    if dash < 0:
284                        break
285                    if offset + dash + 2 < len(s) and s[offset + dash + 1] in 'LI':
286                        s = s[:offset + dash + 2] + top_builddir + s[offset + dash + 2:]
287                    offset += dash + 1
288        return s
289
290    def _filter_duplicates(self, s):
291        clean = ''
292        present = {}
293        ss = shlex.split(s)
294        i = 0
295        while i < len(ss):
296            added = False
297            for do in package.dual_opts:
298                if ss[i].startswith(do):
299                    if ss[i] == do:
300                        i += 1
301                        if i == len(ss):
302                            clean += ' %s' % (do)
303                        else:
304                            key = '%s%s' % (do, ss[i])
305                            if key not in present:
306                                if ' ' in ss[i]:
307                                    clean += ' %s"%s"' % (do, ss[i])
308                                else:
309                                    clean += ' %s' % (key)
310                    else:
311                        key = ss[i]
312                        if key not in present:
313                            clean += ' %s' % (key)
314                    added = True
315                    present[key] = True
316                    break
317            if not added:
318                if ss[i] not in present:
319                    clean += ' %s' % (ss[i])
320                    present[ss[i]] = True
321            i += 1
322        return clean
323
324    def _filter(self, s):
325        s = self._filter_top_builddir(s)
326        s = self._filter_sysroot(s)
327        s = self._filter_duplicates(s)
328        return s.strip()
329
330    def _splitter(self, pkg_list):
331        pkgs = []
332        pls = pkg_list.split()
333        i = 0
334        while i < len(pls):
335            pkg = [pls[i]]
336            i += 1
337            if i < len(pls) and pls[i] in package.version_ops:
338                pkg += [pls[i]]
339                i += 1
340                if i < len(ls):
341                    pkg += [pls[i]]
342                    i += 1
343            pkgs += [pkg]
344        return pkgs
345
346    def name_from_file(self, file = None):
347        if file is None:
348            file = self.file
349        if file is None:
350            return None
351        name = os.path.basename(file)
352        if name.endswith('.pc'):
353            name = name[:-3]
354        return name
355
356    def name(self):
357        return self.name_
358
359    def file(self):
360        return self.file_
361
362    def exists(self):
363        ok = False
364        if self.file_:
365            ok = True
366        if self.libraries:
367            ok = True
368        return ok
369
370    def load(self, name):
371        if name in package.loaded:
372            raise error('package already loaded: %s' % (name))
373        if self.name_:
374            self._clean()
375        self.name_ = name
376        file = self._find_package(name)
377        self._log('load: %s (%s)' % (name, file))
378        if file:
379            if self.src:
380                self.src.writelines('==%s%s' % ('=' * 80, os.linesep))
381                self.src.writelines(' %s %s%s' % (file, '=' * (80 - len(file)), os.linesep))
382                self.src.writelines('==%s%s' % ('=' * 80, os.linesep))
383            f = open(file)
384            tm = False
385            for l in f.readlines():
386                if self.src:
387                    self.src.writelines(l)
388                l = l[:-1]
389                hash = l.find('#')
390                if hash >= 0:
391                    l = l[:hash]
392                if len(l):
393                    d = 0
394                    define = False
395                    eq = l.find('=')
396                    dd = l.find(':')
397                    if eq > 0 and dd > 0:
398                        if eq < dd:
399                            define = True
400                            d = eq
401                        else:
402                            define = False
403                            d = dd
404                    elif eq >= 0:
405                        define = True
406                        d = eq
407                    elif dd >= 0:
408                        define = False
409                        d = dd
410                    if d > 0:
411                        lhs = l[:d].lower()
412                        rhs = l[d + 1:]
413                        if tm:
414                            print('define: ' + str(define) + ', lhs: ' + lhs + ', ' + rhs)
415                        if define:
416                            self.defines[lhs] = rhs
417                        else:
418                            self.fields[lhs] = rhs
419            self.file_ = file
420        else:
421            self.libraries = self._find_libraries(name)
422        for nt in package.node_types:
423            requires = self.get(nt, private = False)
424            if requires:
425                for r in package.splitter(requires):
426                    if r[0] not in self.nodes[nt]:
427                        if r[0] in package.loaded:
428                            pkg = package.loaded[r[0]]
429                        else:
430                            pkg = package(r[0], self.prefix, self.output)
431                        ver = pkg.get('version')
432                        self._log(' checking: %s (%s) %s %s' % (r[0], ver, r[1], r[2]))
433                        if ver and package.check_versions(ver, r[1], r[2]):
434                            self.nodes[nt][r[0]] = pkg
435                        else:
436                            self._log('failed: %s (%s %s %s)' % (r[0], ver, r[1], r[2]))
437                            self.nodes['failed'][r[0]] = pkg
438        if self.exists():
439            package.loaded[name] = self
440
441    def get(self, label, private = True):
442        if label.lower() not in self.fields:
443            return None
444        s = ''
445        if self.file_:
446            mre = re.compile('\$\{[^\}]+\}')
447            s = self.fields[label.lower()]
448            expanded = True
449            tm = False
450            while expanded:
451                expanded = False
452                if tm:
453                    self._log('pc:get: "' + s + '"')
454                ms = mre.findall(s)
455                for m in ms:
456                    mn = m[2:-1]
457                    if mn.lower() in self.defines:
458                        s = s.replace(m, self.defines[mn.lower()])
459                        expanded = True
460            if label in package.get_recursion:
461                for nt in package.node_types:
462                    if 'private' not in nt or ('private' in nt and private):
463                        for n in self.nodes[nt]:
464                            r = self.nodes[nt][n].get(label, private = private)
465                            self._log('node: %s: %s' % (self.nodes[nt][n].name(), r))
466                            if r:
467                                s += ' ' + r
468        elif label == 'libs' and len(self.libraries):
469            s = '-l%s' % (self.name_[3:])
470        return self._filter(s)
471
472    def check(self, op, version):
473        self._log('checking: %s %s %s' % (self.name_, op, version))
474        ok = False
475        if self.file_:
476            pkgver = self.get('version')
477            if pkgver is None:
478                self._log('check: %s %s failed (no version)' % (op, version))
479                return False
480            ok = package.check_versions(pkgver, op, version)
481            if ok:
482                self._log('check: %s %s %s ok' % (pkgver, op, version))
483            else:
484                self._log('check: %s %s %s failed' % (pkgver, op, version))
485        else:
486            if len(self.libraries):
487                ok = True
488            else:
489                self._log('check: %s not found' % (self.name_))
490        return ok
Note: See TracBrowser for help on using the repository browser.