910e62b5创建于 1月15日历史提交
#!/usr/bin/env python
# Copyright 2017 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import re
import sys

import deb_version


class PackageVersionIntervalEndpoint:

    def __init__(self, is_open, is_inclusive, version):
        self._is_open = is_open
        self._is_inclusive = is_inclusive
        self._version = version

    def _intersect(self, other, is_start):
        if self._is_open and other._is_open:
            return self
        if self._is_open:
            return other
        if other._is_open:
            return self
        cmp_code = self._version.__cmp__(other._version)
        if not is_start:
            cmp_code *= -1
        if cmp_code > 0:
            return self
        if cmp_code < 0:
            return other
        if not self._is_inclusive:
            return self
        return other

    def __str__(self):
        return 'PackageVersionIntervalEndpoint(%s, %s, %s)' % (
            self._is_open, self._is_inclusive, self._version)

    def __eq__(self, other):
        if self._is_open and other._is_open:
            return True
        return (self._is_open == other._is_open and
                self._is_inclusive == other._is_inclusive and
                self._version == other._version)


class PackageVersionInterval:

    def __init__(self, string_rep, package, start, end):
        self.string_rep = string_rep
        self.package = package
        self.start = start
        self.end = end

    def contains(self, version):
        if not self.start._is_open:
            if self.start._is_inclusive:
                if version < self.start._version:
                    return False
            elif version <= self.start._version:
                return False
        if not self.end._is_open:
            if self.end._is_inclusive:
                if version > self.end._version:
                    return False
            elif version >= self.end._version:
                return False
        return True

    def intersect(self, other):
        return PackageVersionInterval('', '',
                                      self.start._intersect(other.start, True),
                                      self.end._intersect(other.end, False))

    def implies(self, other):
        if self.package != other.package:
            return False
        return self.intersect(other) == self

    def __str__(self):
        return 'PackageVersionInterval(%s)' % self.string_rep

    def __eq__(self, other):
        return self.start == other.start and self.end == other.end


class PackageVersionIntervalSet:

    def __init__(self, intervals):
        self.intervals = intervals

    def formatted(self):
        return ' | '.join([interval.string_rep for interval in self.intervals])

    def _interval_implies_other_intervals(self, interval, other_intervals):
        for other_interval in other_intervals:
            if interval.implies(other_interval):
                return True
        return False

    def implies(self, other):
        # This disjunction implies |other| if every term in this
        # disjunction implies some term in |other|.
        for interval in self.intervals:
            if not self._interval_implies_other_intervals(
                    interval, other.intervals):
                return False
        return True


def version_interval_endpoints_from_exp(op, version):
    open_endpoint = PackageVersionIntervalEndpoint(True, None, None)
    inclusive_endpoint = PackageVersionIntervalEndpoint(False, True, version)
    exclusive_endpoint = PackageVersionIntervalEndpoint(False, False, version)
    if op == '>=':
        return (inclusive_endpoint, open_endpoint)
    if op == '<=':
        return (open_endpoint, inclusive_endpoint)
    if op == '>>' or op == '>':
        return (exclusive_endpoint, open_endpoint)
    if op == '<<' or op == '<':
        return (open_endpoing, exclusive_endpoint)
    assert op == '='
    return (inclusive_endpoint, inclusive_endpoint)


def parse_dep(dep):
    """Parses a package and version requirement formatted by dpkg-shlibdeps.

  Args:
      dep: A string of the format "package (op version)"

  Returns:
      A PackageVersionInterval.
  """
    package_name_regex = r'[a-z][a-z0-9\+\-\.]+'
    match = re.match('^(%s)$' % package_name_regex, dep)
    if match:
        return PackageVersionInterval(
            dep, match.group(1),
            PackageVersionIntervalEndpoint(True, None, None),
            PackageVersionIntervalEndpoint(True, None, None))
    match = re.match(
        r'^(%s) \(([\>\=\<]+) ([\~0-9A-Za-z\+\-\.\:]+)\)$' % package_name_regex,
        dep)
    if match:
        (start, end) = version_interval_endpoints_from_exp(
            match.group(2), deb_version.DebVersion(match.group(3)))
        return PackageVersionInterval(dep, match.group(1), start, end)
    print >> sys.stderr, 'Failed to parse ' + dep
    sys.exit(1)


def parse_interval_set(deps):
    r"""Parses a disjunction of package version requirements.

  Args:
      deps: A string of the format
          "package \(op version\) (| package \(op version\))*"

  Returns:
      A list of PackageVersionIntervals
  """
    return PackageVersionIntervalSet(
        [parse_dep(dep.strip()) for dep in deps.split('|')])