source: rtems-source-builder/source-builder/sb/pkgconfig.py @ 38ed59a

4.104.95
Last change on this file since 38ed59a was 38ed59a, checked in by Chris Johns <chrisj@…>, on 03/17/16 at 05:39:57

sb: Support --dry-run --with-download for 3rd party RTEMS BSP packages.

The building of 3rd party packages for an RTEMS BSP requires a valid
BSP so the standard method to download the source for releasing does
not work. This change adds support to allow this. The RTEMS BSP support
will not generate an error is no BSP or tools are provided or found.

The change addis logic operators to the %if statement so you can '
'

to 'or' and '&&' to 'and' logic expressions.

A new %log directive has been added to clean up the messages.

A new %{!define ...} has been added to aid checking within logic
expressions.

All command line --with/--without now appear as macros.

Add version.version to get just the RTEMS major and minor version.

Some pkg-config issues have been resolved.

Closes #2655.

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