source: rtems-source-builder/source-builder/sb/pkgconfig.py @ 7385feb

4.11
Last change on this file since 7385feb was 0ffee19, checked in by Chris Johns <chrisj@…>, on 06/15/14 at 05:40:34

sb: Add support for building RTEMS 3rd party packages.

Remove the 'opt' from various macros and shell variables.

Add pkgconfig to the checks to make it clear the check is a
pkgconfig check.

Add NTP support as the first package to be built using the RSB.

Split the RTEMS URL's out from the base bset file into a separate
file that be included by other files.

Add an RTEMS BSP configuration file to help abstract the process
of building 3rd party packages.

Clean the cross and canadian cross support up so we can cleanly support
cross and canadian cross building.

Refactor the pkgconfig support and clean up the PC file handling of
loading modules.

Add support for %{?..} to return false if a macro is %{nil}.

Add %{pkgconfig ..} support to allow better control of access RTEMS
pkgconfig files.

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