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

4.104.114.9
Last change on this file since faa3583 was faa3583, checked in by Chris Johns <chrisj@…>, on Feb 12, 2014 at 2:13:28 AM

sb: Fixed from Fedora testing.

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