source: rtems-source-builder/source-builder/sb/pkgconfig.py @ 55e52f4

4.104.114.9
Last change on this file since 55e52f4 was 55e52f4, checked in by Chris Johns <chrisj@…>, on Feb 11, 2014 at 3:06:07 AM

sb: Do not scan for libraries by default.

  • Property mode set to 100755
File size: 16.8 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, libs_scan = False, output = None, src = None):
164        self._clean()
165        self.name_ = name
166        self.libs_scan = libs_scan
167        self.output = output
168        self.src = src
169        self.prefix = None
170        self.paths = []
171        if prefix is None:
172            prefix = default_prefix()
173        if prefix:
174            if type(prefix) is str:
175                self.prefix = prefix.split(os.pathsep)
176            elif type(prefix) is list:
177                self.prefix = prefix
178            else:
179                raise error('invalid type of prefix: %s' % (type(prefix)))
180            for p in self.prefix:
181                for d in package.config_prefixes:
182                    prefix = os.path.join(p, d, 'pkgconfig')
183                    if os.path.exists(prefix):
184                        self.paths += [prefix]
185            self._log('paths: %s' % (', '.join(self.paths)))
186        if 'PKG_CONFIG_SYSROOT_DIR' in os.environ:
187            self.defines['sysroot'] = os.environ['PKG_CONFIG_SYSROOT_DIR']
188            self._log('sysroot: %s' % (self.defines['sysroot']))
189        if 'PKG_CONFIG_BUILD_TOP_DIR' in os.environ:
190            self.defines['top_builddir'] = os.environ['PKG_CONFIG_BUILD_TOP_DIR']
191            self._log('top_builddir: %s' % (self.defines['top_builddir']))
192        if self.name_:
193            self.load(self.name_)
194
195    def __str__(self):
196        s = self._str()
197        for nt in package.node_types:
198            for n in sorted(self.nodes[nt]):
199                s += ' ' + \
200                     ' '.join(['%s%s' % (s, os.linesep) \
201                               for s in str(self.nodes[nt][n]).split(os.linesep)])
202        return s[:-1]
203
204    def _str(self):
205        if self.file_ or len(self.libraries):
206            e = 'E'
207        else:
208            e = '-'
209        s = '> %s %s (%s)%s' % (self.name_, e, self.file_, os.linesep)
210        for d in sorted(self.defines):
211            s += 'd: %s: %s%s' % (d, self.defines[d], os.linesep)
212        for f in sorted(self.fields):
213            s += 'f: %s: %s%s' % (f, self.fields[f], os.linesep)
214        for l in sorted(self.libraries):
215            s += 'l: %s%s' % (l, os.linesep)
216        for nt in package.node_types + ['failed']:
217            s += '%s: ' % (package.node_type_labels[nt])
218            if len(self.nodes[nt]):
219                txt = []
220                for n in sorted(self.nodes[nt]):
221                    if self.nodes[nt][n].exists():
222                        e = ''
223                    else:
224                        e = '*'
225                    txt += ['%s%s' % (n, e)]
226                s += '%s%s' % (', '.join(txt), os.linesep)
227            else:
228                s += 'none' + os.linesep
229        return s #[:-1]
230
231    def _clean(self):
232        self.name_ = None
233        self.file_ = None
234        self.defines = {}
235        self.fields = {}
236        self.nodes = { 'failed': {} }
237        for nt in package.node_types:
238            self.nodes[nt] = {}
239        self.libraries = []
240
241    def _log(self, s):
242        if self.output:
243            self.output(s)
244
245    def _find_package(self, name):
246        if len(self.paths):
247            for path in self.paths:
248                pc = os.path.join(path, '%s.pc' % (name))
249                if os.path.isfile(pc):
250                    return pc;
251        return None
252
253    def _find_libraries(self, name):
254        libraries = []
255        if self.libs_scan:
256            for prefix in self.prefix:
257                prefix = os.path.join(prefix, 'lib')
258                if os.path.exists(prefix):
259                    for l in os.listdir(prefix):
260                        if l.startswith(name + '.'):
261                            libraries += [os.path.join(prefix, l)]
262                            break
263        return libraries
264
265    def _filter_sysroot(self, s):
266        if 'sysroot' in self.defines:
267            sysroot = self.defines['sysroot']
268            offset = 0
269            while True:
270                dash = s[offset:].find('-')
271                if dash < 0:
272                    break
273                if offset + dash + 2 < len(s) and s[offset + dash + 1] in 'LI':
274                    s = s[:offset + dash + 2] + sysroot + s[offset + dash + 2:]
275                offset += dash + 1
276        return s
277
278    def _filter_top_builddir(self, s):
279        if 'top_builddir' in self.defines:
280            top_builddir = self.defines['top_builddir']
281            if self.file.startswith(top_builddir) and not s.startswith(top_builddir):
282                offset = 0
283                while True:
284                    dash = s[offset:].find('-')
285                    if dash < 0:
286                        break
287                    if offset + dash + 2 < len(s) and s[offset + dash + 1] in 'LI':
288                        s = s[:offset + dash + 2] + top_builddir + s[offset + dash + 2:]
289                    offset += dash + 1
290        return s
291
292    def _filter_duplicates(self, s):
293        clean = ''
294        present = {}
295        ss = shlex.split(s)
296        i = 0
297        while i < len(ss):
298            added = False
299            for do in package.dual_opts:
300                if ss[i].startswith(do):
301                    if ss[i] == do:
302                        i += 1
303                        if i == len(ss):
304                            clean += ' %s' % (do)
305                        else:
306                            key = '%s%s' % (do, ss[i])
307                            if key not in present:
308                                if ' ' in ss[i]:
309                                    clean += ' %s"%s"' % (do, ss[i])
310                                else:
311                                    clean += ' %s' % (key)
312                    else:
313                        key = ss[i]
314                        if key not in present:
315                            clean += ' %s' % (key)
316                    added = True
317                    present[key] = True
318                    break
319            if not added:
320                if ss[i] not in present:
321                    clean += ' %s' % (ss[i])
322                    present[ss[i]] = True
323            i += 1
324        return clean
325
326    def _filter(self, s):
327        s = self._filter_top_builddir(s)
328        s = self._filter_sysroot(s)
329        s = self._filter_duplicates(s)
330        return s.strip()
331
332    def _splitter(self, pkg_list):
333        pkgs = []
334        pls = pkg_list.split()
335        i = 0
336        while i < len(pls):
337            pkg = [pls[i]]
338            i += 1
339            if i < len(pls) and pls[i] in package.version_ops:
340                pkg += [pls[i]]
341                i += 1
342                if i < len(ls):
343                    pkg += [pls[i]]
344                    i += 1
345            pkgs += [pkg]
346        return pkgs
347
348    def name_from_file(self, file = None):
349        if file is None:
350            file = self.file
351        if file is None:
352            return None
353        name = os.path.basename(file)
354        if name.endswith('.pc'):
355            name = name[:-3]
356        return name
357
358    def name(self):
359        return self.name_
360
361    def file(self):
362        return self.file_
363
364    def exists(self):
365        ok = False
366        if self.file_:
367            ok = True
368        if self.libraries:
369            ok = True
370        return ok
371
372    def load(self, name):
373        if name in package.loaded:
374            raise error('package already loaded: %s' % (name))
375        if self.name_:
376            self._clean()
377        self.name_ = name
378        file = self._find_package(name)
379        if file:
380            self._log('load: %s (%s)' % (name, file))
381            if self.src:
382                self.src.writelines('==%s%s' % ('=' * 80, os.linesep))
383                self.src.writelines(' %s %s%s' % (file, '=' * (80 - len(file)), os.linesep))
384                self.src.writelines('==%s%s' % ('=' * 80, os.linesep))
385            f = open(file)
386            tm = False
387            for l in f.readlines():
388                if self.src:
389                    self.src.writelines(l)
390                l = l[:-1]
391                hash = l.find('#')
392                if hash >= 0:
393                    l = l[:hash]
394                if len(l):
395                    d = 0
396                    define = False
397                    eq = l.find('=')
398                    dd = l.find(':')
399                    if eq > 0 and dd > 0:
400                        if eq < dd:
401                            define = True
402                            d = eq
403                        else:
404                            define = False
405                            d = dd
406                    elif eq >= 0:
407                        define = True
408                        d = eq
409                    elif dd >= 0:
410                        define = False
411                        d = dd
412                    if d > 0:
413                        lhs = l[:d].lower()
414                        rhs = l[d + 1:]
415                        if tm:
416                            print('define: ' + str(define) + ', lhs: ' + lhs + ', ' + rhs)
417                        if define:
418                            self.defines[lhs] = rhs
419                        else:
420                            self.fields[lhs] = rhs
421            self.file_ = file
422        else:
423            self._log('load: %s (libraries)' % (name))
424            self.libraries = self._find_libraries(name)
425        for nt in package.node_types:
426            requires = self.get(nt, private = False)
427            if requires:
428                for r in package.splitter(requires):
429                    if r[0] not in self.nodes[nt]:
430                        if r[0] in package.loaded:
431                            pkg = package.loaded[r[0]]
432                        else:
433                            pkg = package(r[0], self.prefix, self.output)
434                        ver = pkg.get('version')
435                        self._log(' checking: %s (%s) %s %s' % (r[0], ver, r[1], r[2]))
436                        if ver and package.check_versions(ver, r[1], r[2]):
437                            self.nodes[nt][r[0]] = pkg
438                        else:
439                            self._log('failed: %s (%s %s %s)' % (r[0], ver, r[1], r[2]))
440                            self.nodes['failed'][r[0]] = pkg
441        if self.exists():
442            self._log('load: exists')
443            package.loaded[name] = self
444
445    def get(self, label, private = True):
446        if label.lower() not in self.fields:
447            return None
448        s = ''
449        if self.file_:
450            mre = re.compile('\$\{[^\}]+\}')
451            s = self.fields[label.lower()]
452            expanded = True
453            tm = False
454            while expanded:
455                expanded = False
456                if tm:
457                    self._log('pc:get: "' + s + '"')
458                ms = mre.findall(s)
459                for m in ms:
460                    mn = m[2:-1]
461                    if mn.lower() in self.defines:
462                        s = s.replace(m, self.defines[mn.lower()])
463                        expanded = True
464            if label in package.get_recursion:
465                for nt in package.node_types:
466                    if 'private' not in nt or ('private' in nt and private):
467                        for n in self.nodes[nt]:
468                            r = self.nodes[nt][n].get(label, private = private)
469                            self._log('node: %s: %s' % (self.nodes[nt][n].name(), r))
470                            if r:
471                                s += ' ' + r
472        elif label == 'libs' and len(self.libraries):
473            s = '-l%s' % (self.name_[3:])
474        return self._filter(s)
475
476    def check(self, op, version):
477        self._log('checking: %s %s %s' % (self.name_, op, version))
478        ok = False
479        if self.file_:
480            pkgver = self.get('version')
481            if pkgver is None:
482                self._log('check: %s %s failed (no version)' % (op, version))
483                return False
484            ok = package.check_versions(pkgver, op, version)
485            if ok:
486                self._log('check: %s %s %s ok' % (pkgver, op, version))
487            else:
488                self._log('check: %s %s %s failed' % (pkgver, op, version))
489        else:
490            if len(self.libraries):
491                ok = True
492            else:
493                self._log('check: %s not found' % (self.name_))
494        return ok
Note: See TracBrowser for help on using the repository browser.