| # Copyright 2015 Gentoo Foundation |
| # Distributed under the terms of the GNU General Public License v2 |
| |
| from __future__ import unicode_literals |
| |
| import collections |
| import fnmatch |
| import functools |
| from itertools import chain |
| import os |
| import re |
| |
| from portage.util import shlex_split |
| from portage.util import ( |
| normalize_path, |
| varexpand, |
| ) |
| |
| |
| class SonameDepsProcessor(object): |
| """ |
| Processes NEEDED.ELF.2 entries for one package, in order to generate |
| REQUIRES and PROVIDES metadata. |
| |
| Any sonames provided by the package will automatically be filtered |
| from the generated REQUIRES values. |
| """ |
| |
| def __init__(self, provides_exclude, requires_exclude): |
| """ |
| @param provides_exclude: PROVIDES_EXCLUDE value |
| @type provides_exclude: str |
| @param requires_exclude: REQUIRES_EXCLUDE value |
| @type requires_exclude: str |
| """ |
| self._provides_exclude = self._exclude_pattern(provides_exclude) |
| self._requires_exclude = self._exclude_pattern(requires_exclude) |
| self._requires_map = collections.defaultdict( |
| functools.partial(collections.defaultdict, set)) |
| self._provides_map = {} |
| self._provides_unfiltered = {} |
| self._basename_map = {} |
| self._provides = None |
| self._requires = None |
| self._intersected = False |
| |
| @staticmethod |
| def _exclude_pattern(s): |
| # shlex_split enables quoted whitespace inside patterns |
| if s: |
| pat = re.compile("|".join( |
| fnmatch.translate(x.lstrip(os.sep)) |
| for x in shlex_split(s))) |
| else: |
| pat = None |
| return pat |
| |
| def add(self, entry): |
| """ |
| Add one NEEDED.ELF.2 entry, for inclusion in the generated |
| REQUIRES and PROVIDES values. |
| |
| @param entry: NEEDED.ELF.2 entry |
| @type entry: NeededEntry |
| """ |
| |
| multilib_cat = entry.multilib_category |
| if multilib_cat is None: |
| # This usage is invalid. The caller must ensure that |
| # the multilib category data is supplied here. |
| raise AssertionError( |
| "Missing multilib category data: %s" % entry.filename) |
| |
| self._basename_map.setdefault( |
| os.path.basename(entry.filename), []).append(entry) |
| |
| if entry.needed and ( |
| self._requires_exclude is None or |
| self._requires_exclude.match( |
| entry.filename.lstrip(os.sep)) is None): |
| runpaths = frozenset() |
| if entry.runpaths is not None: |
| expand = {"ORIGIN": os.path.dirname(entry.filename)} |
| runpaths = frozenset(normalize_path(varexpand(x, expand, |
| error_leader=lambda: "%s: DT_RUNPATH: " % entry.filename)) |
| for x in entry.runpaths) |
| for x in entry.needed: |
| if (self._requires_exclude is None or |
| self._requires_exclude.match(x) is None): |
| self._requires_map[multilib_cat][x].add(runpaths) |
| |
| if entry.soname: |
| self._provides_unfiltered.setdefault( |
| multilib_cat, set()).add(entry.soname) |
| |
| if entry.soname and ( |
| self._provides_exclude is None or |
| (self._provides_exclude.match( |
| entry.filename.lstrip(os.sep)) is None and |
| self._provides_exclude.match(entry.soname) is None)): |
| self._provides_map.setdefault( |
| multilib_cat, set()).add(entry.soname) |
| |
| def _intersect(self): |
| requires_map = self._requires_map |
| provides_map = self._provides_map |
| provides_unfiltered = self._provides_unfiltered |
| |
| for multilib_cat in set(chain(requires_map, provides_map)): |
| provides_map.setdefault(multilib_cat, set()) |
| provides_unfiltered.setdefault(multilib_cat, set()) |
| for soname, consumers in list(requires_map[multilib_cat].items()): |
| if soname in provides_unfiltered[multilib_cat]: |
| del requires_map[multilib_cat][soname] |
| elif soname in self._basename_map: |
| # Handle internal libraries that lack an soname, which |
| # are resolved via DT_RUNPATH, see ebtables for example |
| # (bug 646190). |
| for entry in self._basename_map[soname]: |
| if entry.multilib_category != multilib_cat: |
| continue |
| dirname = os.path.dirname(entry.filename) |
| for runpaths in list(consumers): |
| if dirname in runpaths: |
| consumers.remove(runpaths) |
| if not consumers: |
| del requires_map[multilib_cat][soname] |
| break |
| |
| provides_data = [] |
| for multilib_cat in sorted(provides_map): |
| if provides_map[multilib_cat]: |
| provides_data.append(multilib_cat + ":") |
| provides_data.extend(sorted(provides_map[multilib_cat])) |
| |
| if provides_data: |
| self._provides = " ".join(provides_data) + "\n" |
| |
| requires_data = [] |
| for multilib_cat in sorted(requires_map): |
| if requires_map[multilib_cat]: |
| requires_data.append(multilib_cat + ":") |
| requires_data.extend(sorted(requires_map[multilib_cat])) |
| |
| if requires_data: |
| self._requires = " ".join(requires_data) + "\n" |
| |
| self._intersected = True |
| |
| @property |
| def provides(self): |
| """ |
| @rtype: str |
| @return: PROVIDES value generated from NEEDED.ELF.2 entries |
| """ |
| if not self._intersected: |
| self._intersect() |
| return self._provides |
| |
| @property |
| def requires(self): |
| """ |
| @rtype: str |
| @return: REQUIRES value generated from NEEDED.ELF.2 entries |
| """ |
| if not self._intersected: |
| self._intersect() |
| return self._requires |