source: rtems-source-builder/source-builder/sb/pkgconfig.py @ 339f92f

4.104.114.95
Last change on this file since 339f92f was ecda605, checked in by Chris Johns <chrisj@…>, on 04/08/14 at 06:16:50

sb: Fix pkg-config to handle quoted libraries.

  • Property mode set to 100755
File size: 17.3 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 os
38import os.path
39import re
40import shlex
41import sys
42
43def default_prefix(common = True):
44    paths = []
45    if 'PKG_CONFIG_PATH' in os.environ:
46        paths += os.environ['PKG_CONFIG_PATH'].split(':')
47    if common:
48        defaults = ['/usr', '/usr/share', '/lib', '/lib64', '/usr/lib', '/usr/lib64', '/usr/local']
49        for d in defaults:
50            for cp in package.config_prefixes:
51                prefix = os.path.join(d, cp, 'pkgconfig')
52                if os.path.exists(prefix):
53                    paths += [prefix]
54    return paths
55
56class error(Exception):
57    def __init__(self, msg):
58        self.msg = msg
59
60    def __str__(self):
61        return self.msg
62
63class package(object):
64
65    node_types = ['requires', 'requires.private']
66    node_type_labels = { 'requires': 'r', 'requires.private': 'rp', 'failed': 'F' }
67    version_ops = ['=', '<', '>', '<=', '>=', '!=']
68    config_prefixes = ['lib', 'libdata']
69    get_recursion = ['cflags', 'libs']
70    no_dup_flags = ['-I', '-l', '-L']
71    dual_opts = ['-D', '-U', '-I', '-l', '-L']
72    lib_list_splitter = re.compile('[\s,]+')
73    loaded = {}
74
75    @staticmethod
76    def is_version(v):
77        for n in v.split('.'):
78            if not n.isdigit():
79                return False
80        return True
81
82    @staticmethod
83    def splitter(pkg_list):
84        pkgs = []
85        if type(pkg_list) == list:
86            pls = []
87            for p in pkg_list:
88                pls += package.lib_list_splitter.split(p)
89        else:
90            pls = package.lib_list_splitter.split(pkg_list)
91        i = 0
92        while i < len(pls):
93            pkg = [pls[i]]
94            i += 1
95            if i < len(pls):
96                op = None
97                if package.is_version(pls[i]):
98                    op = '>='
99                    ver = pls[i]
100                    i += 1
101                elif pls[i] in package.version_ops:
102                    op = pls[i]
103                    i += 1
104                    if i < len(pls):
105                        ver = pls[i]
106                        i += 1
107                else:
108                    op = '>='
109                    ver = '0'
110                pkg += [op, ver]
111            else:
112                pkg += ['>=', '0']
113            pkgs += [pkg]
114        return pkgs
115
116    @staticmethod
117    def check_versions(lhs, op, rhs):
118        if op not in package.version_ops:
119            raise error('bad operator: %s' % (op))
120        if not lhs or not rhs:
121            return False
122        slhs = lhs.split('.')
123        srhs = rhs.split('.')
124        ok = True
125        i = 0
126        while i < len(srhs):
127            try:
128                l = int(slhs[i])
129                r = int(srhs[i])
130            except:
131                return False
132            if op == '=':
133                if l != r:
134                    ok = False
135                    break
136            elif op == '<':
137                if l < r:
138                    break
139                if l >= r:
140                    ok = False
141                    break
142            elif op == '>':
143                if l > r:
144                    break
145                if l <= r:
146                    ok = False
147                    break
148            elif op == '<=':
149                if l < r:
150                    break
151                if l > r:
152                    ok = False
153                    break
154            elif op == '>=':
155                if l > r:
156                    break
157                if l < r:
158                    ok = False
159                    break
160            elif op == '!=':
161                if l != r:
162                    ok = True
163                    break
164                if l == r:
165                    ok = False
166            i += 1
167        return ok
168
169    @staticmethod
170    def dump_loaded():
171        for n in sorted(package.loaded):
172            print package.loaded[n]._str()
173
174    def __init__(self, name = None, prefix = None, libs_scan = False, output = None, src = None):
175        self._clean()
176        self.name_ = name
177        self.libs_scan = libs_scan
178        self.output = output
179        self.src = src
180        self.prefix = None
181        self.paths = []
182        if prefix is None:
183            prefix = default_prefix()
184        if prefix:
185            if type(prefix) is str:
186                self.prefix = prefix.split(os.pathsep)
187            elif type(prefix) is list:
188                self.prefix = prefix
189            else:
190                raise error('invalid type of prefix: %s' % (type(prefix)))
191            for p in self.prefix:
192                if os.path.exists(p):
193                    self.paths += [p]
194            self._log('paths: %s' % (', '.join(self.paths)))
195        if 'sysroot' in self.defines:
196            self._log('sysroot: %s' % (self.defines['sysroot']))
197        if 'top_builddir' in self.defines:
198            self._log('top_builddir: %s' % (self.defines['top_builddir']))
199        if self.name_:
200            self.load(self.name_)
201
202    def __str__(self):
203        s = self._str()
204        for nt in package.node_types:
205            for n in sorted(self.nodes[nt]):
206                s += ' ' + \
207                     ' '.join(['%s%s' % (s, os.linesep) \
208                               for s in str(self.nodes[nt][n]).split(os.linesep)])
209        return s[:-1]
210
211    def _str(self):
212        if self.file_ or len(self.libraries):
213            e = 'E'
214        else:
215            e = '-'
216        s = '> %s %s (%s)%s' % (self.name_, e, self.file_, os.linesep)
217        for d in sorted(self.defines):
218            s += 'd: %s: %s%s' % (d, self.defines[d], os.linesep)
219        for f in sorted(self.fields):
220            s += 'f: %s: %s%s' % (f, self.fields[f], os.linesep)
221        for l in sorted(self.libraries):
222            s += 'l: %s%s' % (l, os.linesep)
223        for nt in package.node_types + ['failed']:
224            s += '%s: ' % (package.node_type_labels[nt])
225            if len(self.nodes[nt]):
226                txt = []
227                for n in sorted(self.nodes[nt]):
228                    if self.nodes[nt][n].exists():
229                        e = ''
230                    else:
231                        e = '*'
232                    txt += ['%s%s' % (n, e)]
233                s += '%s%s' % (', '.join(txt), os.linesep)
234            else:
235                s += 'none' + os.linesep
236        return s #[:-1]
237
238    def _clean(self):
239        self.name_ = None
240        self.file_ = None
241        self.defines = {}
242        self.fields = {}
243        self.nodes = { 'failed': {} }
244        for nt in package.node_types:
245            self.nodes[nt] = {}
246        self.libraries = []
247        if 'PKG_CONFIG_SYSROOT_DIR' in os.environ:
248            self.defines['sysroot'] = os.environ['PKG_CONFIG_SYSROOT_DIR']
249        if 'PKG_CONFIG_BUILD_TOP_DIR' in os.environ:
250            self.defines['top_builddir'] = os.environ['PKG_CONFIG_BUILD_TOP_DIR']
251
252    def _log(self, s):
253        if self.output:
254            self.output(s)
255
256    def _find_package(self, name):
257        if len(self.paths):
258            for path in self.paths:
259                pc = os.path.join(path, '%s.pc' % (name))
260                if os.path.isfile(pc):
261                    return pc;
262        return None
263
264    def _find_libraries(self, name):
265        libraries = []
266        if self.libs_scan:
267            for prefix in self.prefix:
268                prefix = os.path.join(prefix, 'lib')
269                if os.path.exists(prefix):
270                    for l in os.listdir(prefix):
271                        if l.startswith(name + '.'):
272                            libraries += [os.path.join(prefix, l)]
273                            break
274        return libraries
275
276    def _filter_sysroot(self, s):
277        if 'sysroot' in self.defines:
278            sysroot = self.defines['sysroot']
279            offset = 0
280            while True:
281                dash = s[offset:].find('-')
282                if dash < 0:
283                    break
284                if offset + dash + 2 < len(s) and s[offset + dash + 1] in 'LI':
285                    s = s[:offset + dash + 2] + sysroot + s[offset + dash + 2:]
286                offset += dash + 1
287        return s
288
289    def _filter_top_builddir(self, s):
290        if 'top_builddir' in self.defines:
291            top_builddir = self.defines['top_builddir']
292            if self.file_.startswith(top_builddir):
293                offset = 0
294                while True:
295                    dash = s[offset:].find('-')
296                    if dash < 0:
297                        break
298                    if offset + dash + 2 < len(s) and s[offset + dash + 1] in 'LI':
299                        path = s[offset + dash + 2:]
300                        if not path.startswith(top_builddir):
301                            s = s[:offset + dash + 2] + top_builddir + path
302                    offset += dash + 1
303        return s
304
305    def _filter_duplicates(self, s):
306        clean = ''
307        present = {}
308        ss = shlex.split(s)
309        i = 0
310        while i < len(ss):
311            added = False
312            for do in package.dual_opts:
313                if ss[i].startswith(do):
314                    if ss[i] == do:
315                        i += 1
316                        if i == len(ss):
317                            clean += ' %s' % (do)
318                        else:
319                            key = '%s%s' % (do, ss[i])
320                            if key not in present:
321                                if ' ' in ss[i]:
322                                    clean += ' %s"%s"' % (do, ss[i])
323                                else:
324                                    clean += ' %s' % (key)
325                    else:
326                        key = ss[i]
327                        if key not in present:
328                            clean += ' %s' % (key)
329                    added = True
330                    present[key] = True
331                    break
332            if not added:
333                if ss[i] not in present:
334                    clean += ' %s' % (ss[i])
335                    present[ss[i]] = True
336            i += 1
337        return clean
338
339    def _filter(self, s):
340        s = self._filter_top_builddir(s)
341        s = self._filter_sysroot(s)
342        s = self._filter_duplicates(s)
343        return s.strip()
344
345    def _splitter(self, pkg_list):
346        pkgs = []
347        pls = pkg_list.split()
348        i = 0
349        while i < len(pls):
350            pkg = [pls[i]]
351            i += 1
352            if i < len(pls) and pls[i] in package.version_ops:
353                pkg += [pls[i]]
354                i += 1
355                if i < len(ls):
356                    pkg += [pls[i]]
357                    i += 1
358            pkgs += [pkg]
359        return pkgs
360
361    def name_from_file(self, file = None):
362        if file is None:
363            file = self.file_
364        if file is None:
365            return None
366        name = os.path.basename(file)
367        if name.endswith('.pc'):
368            name = name[:-3]
369        return name
370
371    def name(self):
372        return self.name_
373
374    def file(self):
375        return self.file_
376
377    def exists(self):
378        ok = False
379        if self.file_:
380            ok = True
381        if self.libraries:
382            ok = True
383        return ok
384
385    def load(self, name):
386        if name in package.loaded:
387            raise error('package already loaded: %s' % (name))
388        if self.name_:
389            self._clean()
390        self.name_ = name
391        file = self._find_package(name)
392        if file:
393            self._log('load: %s (%s)' % (name, file))
394            if self.src:
395                self.src.writelines('==%s%s' % ('=' * 80, os.linesep))
396                self.src.writelines(' %s %s%s' % (file, '=' * (80 - len(file)), os.linesep))
397                self.src.writelines('==%s%s' % ('=' * 80, os.linesep))
398            f = open(file)
399            tm = False
400            for l in f.readlines():
401                if self.src:
402                    self.src.writelines(l)
403                l = l[:-1]
404                hash = l.find('#')
405                if hash >= 0:
406                    l = l[:hash]
407                if len(l):
408                    d = 0
409                    define = False
410                    eq = l.find('=')
411                    dd = l.find(':')
412                    if eq > 0 and dd > 0:
413                        if eq < dd:
414                            define = True
415                            d = eq
416                        else:
417                            define = False
418                            d = dd
419                    elif eq >= 0:
420                        define = True
421                        d = eq
422                    elif dd >= 0:
423                        define = False
424                        d = dd
425                    if d > 0:
426                        lhs = l[:d].lower()
427                        rhs = l[d + 1:]
428                        if tm:
429                            print('define: ' + str(define) + ', lhs: ' + lhs + ', ' + rhs)
430                        if define:
431                            self.defines[lhs] = rhs
432                        else:
433                            self.fields[lhs] = rhs
434            self.file_ = file
435        else:
436            self.libraries = self._find_libraries(name)
437        for nt in package.node_types:
438            requires = self.get(nt, private = False)
439            if requires:
440                for r in package.splitter(requires):
441                    if r[0] not in self.nodes[nt]:
442                        if r[0] in package.loaded:
443                            pkg = package.loaded[r[0]]
444                        else:
445                            pkg = package(r[0], self.prefix, self.output)
446                        ver = pkg.get('version')
447                        self._log(' checking: %s (%s) %s %s' % (r[0], ver, r[1], r[2]))
448                        if ver and package.check_versions(ver, r[1], r[2]):
449                            self.nodes[nt][r[0]] = pkg
450                        else:
451                            self._log('failed: %s (%s %s %s)' % (r[0], ver, r[1], r[2]))
452                            self.nodes['failed'][r[0]] = pkg
453        if self.exists():
454            self._log('load: exists')
455            package.loaded[name] = self
456
457    def get(self, label, private = True):
458        if label.lower() not in self.fields:
459            return None
460        s = ''
461        if self.file_:
462            mre = re.compile('\$\{[^\}]+\}')
463            s = self.fields[label.lower()]
464            expanded = True
465            tm = False
466            while expanded:
467                expanded = False
468                if tm:
469                    self._log('pc:get: "' + s + '"')
470                ms = mre.findall(s)
471                for m in ms:
472                    mn = m[2:-1]
473                    if mn.lower() in self.defines:
474                        s = s.replace(m, self.defines[mn.lower()])
475                        expanded = True
476            if label in package.get_recursion:
477                for nt in package.node_types:
478                    if 'private' not in nt or ('private' in nt and private):
479                        for n in self.nodes[nt]:
480                            r = self.nodes[nt][n].get(label, private = private)
481                            self._log('node: %s: %s' % (self.nodes[nt][n].name(), r))
482                            if r:
483                                s += ' ' + r
484        elif label == 'libs' and len(self.libraries):
485            s = '-l%s' % (self.name_[3:])
486        return self._filter(s)
487
488    def check(self, op, version):
489        self._log('checking: %s %s %s' % (self.name_, op, version))
490        ok = False
491        if self.file_:
492            pkgver = self.get('version')
493            if pkgver is None:
494                self._log('check: %s %s failed (no version)' % (op, version))
495                return False
496            ok = package.check_versions(pkgver, op, version)
497            if ok:
498                self._log('check: %s %s %s ok' % (pkgver, op, version))
499            else:
500                self._log('check: %s %s %s failed' % (pkgver, op, version))
501        else:
502            if len(self.libraries):
503                ok = True
504            else:
505                self._log('check: %s not found' % (self.name_))
506        return ok
Note: See TracBrowser for help on using the repository browser.