source: rtems-source-builder/source-builder/sb/pkgconfig.py @ 650c6f9

Last change on this file since 650c6f9 was 650c6f9, checked in by Chris Johns <chrisj@…>, on 08/25/20 at 11:21:50

sb: Use shebang env python

Closes #4037

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