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

5
Last change on this file since e32e184 was e32e184, checked in by Stephen Clark <stephen.clark@…>, on 09/23/20 at 21:00:55

pkgconfig.py: Removed use of "unicode" keyword for python3 compatibility

Closes #4094.

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