Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
crossover / opt / cxoffice / lib / python / cxfixesobjs.py
Size: Mime:
# (c) Copyright 2014. CodeWeavers, Inc.

"""Defines classes to represent and manipulate the CXFixes information."""

import re


#####
#
# Helper functions for validating fields
#
#####

def _validate_regular_expression(pattern, location, allow_empty=True):
    if pattern:
        try:
            if re.match(pattern, '') and not allow_empty:
                raise AttributeError("the %s regular expression for %s is overly broad" % (pattern, location))
        except re.error:
            raise AttributeError("%s is an invalid regular expression for %s" % (pattern, location))


#####
#
# Helper functions for dumping the profiles
#
#####

def _dump_value(out, indent, value):
    if hasattr(value, 'dump'):
        out.write(" %s\n" % type(value))
        value.dump(out, indent + "| ")
    else:
        out.write(" %s\n" % repr(value))

def _dump_fields(out, indent, obj, fields):
    for field in fields:
        if field not in obj.__dict__:
            continue
        value = obj.__dict__[field]
        if isinstance(value, dict):
            for key in sorted(value.keys()):
                out.write("%s%s{%s} =" % (indent, field, key))
                _dump_value(out, indent, value[key])
        elif isinstance(value, list):
            for i in xrange(len(value)):
                out.write("%s%s[%d] =" % (indent, field, i))
                _dump_value(out, indent, value[i])
        else:
            out.write("%s%s =" % (indent, field))
            _dump_value(out, indent, value)


#####
#
# CXDistribution & co
#
#####

class CXDistGlob(object):
    """Contains the information needed to detect a given distribution."""

    ### Property defaults

    # These class variables provide defaults in case the corresponding
    # instance variable is not set. They should all be immutable objects,

    # This distribution can only be a match for the current system if it
    # contains a file matching this glob.
    # This field is mandatory and thus defaults to None.
    file_glob = None

    # If this list is empty, then finding a file matching the above glob is
    # enough. Otherwise the content of that file must also match all the
    # regular expression patterns in this list in order for the distribution
    # to be a match. This field is optional.
    patterns = tuple()

    def __init__(self, file_glob, patterns=None):
        self.file_glob = file_glob
        if patterns:
            self.patterns = patterns

    ### Instance validation and dumping

    def validate(self, distid):
        """Checks that all the mandatory fields have been set for this
        distribution glob.
        """
        if not self.file_glob:
            raise AttributeError("CXDistGlob.file_glob is not set in %s" % distid)
        for pattern in self.patterns:
            _validate_regular_expression(pattern, "the CXDistGlob.patterns field of %s" % distid)
        return True

    def dump(self, out, indent=""):
        _dump_fields(out, indent, self, ('file_glob', 'patterns'))


class CXDistribution(object):
    """Describes a distribution: its name, how to install packages, etc."""

    ### Property defaults

    # These class variables provide defaults in case the corresponding
    # instance variable is not set. They should all be immutable objects,

    # This distribution's identifier.
    # This field is mandatory and thus defaults to None.
    distid = None

    # A more human-readable name for this distribution.
    # This field is mandatory and thus defaults to None.
    name = None

    # The priority is used to pick a distribution in case more than one seems
    # to match the current platform. This field is optional.
    priority = 0

    # The command to run to cause the distribution to refresh its known
    # packages list. This field is optional.
    updatecmd = None

    # The command to run to install the needed packages. This assumes it can
    # simply be given a space-separated list of packages to install.
    # This field is mandatory and thus defaults to None.
    packagecmd = None

    # A list of CXDistGlobs to be used to detect the current platform.
    # This field is mandatory and thus defaults to None.
    globs = None

    # A list of distribution ids to be used as a fallback if there is no fix
    # for this one. This field is optional.
    fallback = None

    def __init__(self, distid, name, priority):
        self.distid = distid
        self.name = name
        self.priority = priority

    def add_glob(self, glob):
        if self.globs is None:
            self.globs = [glob]
        else:
            self.globs.append(glob)

    ### Instance validation and dumping

    def validate(self):
        """Checks that all the mandatory fields have been set for this
        distribution.

        Note that this only checks internal consistency, not consistency with
        other distributions.
        """
        if not self.distid:
            raise AttributeError("CXDistribution.distid is not set")
        if not self.name:
            raise AttributeError("CXDistribution.name is not set")
        # Note that in the future distributions may define other methods of
        # fixing errors than 'packagecmd', and may not support 'packagecmd'.
        # So don't require 'packagecmd' to be set so old and new-style
        # distributions can be mixed in a single XML file.
        for glob in self.globs:
            glob.validate(self.distid)
        return True

    def dump(self, out, indent=""):
        _dump_fields(out, indent, self, ('distid', 'name', 'priority', 'updatecmd', 'packagecmd', 'globs', 'fallback'))


#####
#
# CXFix & co
#
#####

class CXDistFix(object):
    """Describes how to fix a specific issue on the specified platform."""

    ### Property defaults

    # These class variables provide defaults in case the corresponding
    # instance variable is not set. They should all be immutable objects,

    # The identifier of the distribution this fix applies to.
    # This field is mandatory and thus defaults to None.
    distid = None

    # The bitness this fix applies to. This is either '32', '64', or None if the
    # fix bitness-independent. This field is optional.
    bitness = None

    # A set of packages to install to fix this issue. If this set is empty
    # it means there is no fix for this distribution+bitness combination.
    # This field is optional.
    packages = frozenset()

    def __init__(self, distid, bitness):
        self.distid = distid
        self.bitness = bitness

    ### Instance validation and dumping

    BITNESSES = frozenset((None, '32', '64'))

    # A regular expression to validate package names
    _PACKAGE_RE = re.compile(r'^[a-zA-Z0-9+_.:-]+$')

    def display_name(self, errid):
        if errid:
            name = errid + "/" + self.distid
        else:
            name = self.distid
        if self.bitness:
            name += ":" + self.bitness
        return name

    def validate(self, errid):
        """Checks that all the mandatory fields have been set for this
        distribution fix.

        Note that this only checks internal consistency, not consistency with
        the known distributions list.
        """
        if not self.distid:
            raise AttributeError("CXDistFix.distid is not set in issue %s" % errid)
        if self.bitness not in CXDistFix.BITNESSES:
            raise AttributeError("Invalid bitness value %s for %s", (self.bitness, errid, self.distid))
        for package in self.packages:
            if not CXDistFix._PACKAGE_RE.search(package):
                raise AttributeError("invalid package name '%s' in %s" % (package, self.display_name(errid)))
        return True

    def dump(self, out, indent=""):
        _dump_fields(out, indent, self, ('distid', 'bitness', 'packages'))


class CXFix(object):
    """Groups the known fixes for a given error."""

    ### Property defaults

    # These class variables provide defaults in case the corresponding
    # instance variable is not set. They should all be immutable objects,

    # The identifier of the error this provides fixes for.
    # This field is mandatory and thus defaults to None.
    errid = None

    # Specifies the id of an error that this one is tied to. Whenever the
    # 'tiedto' error is present, assume this one is too, even if cxdiag did not
    # report it. This field is optional.
    tiedto = frozenset()

    # A set of distribution-specific fixes, indexed by the distribution id and
    # bitness tuple. If bitness is irrelevant then None is used.
    # This field is mandatory and thus defaults to None.
    distfixes = None

    def __init__(self, errid, tiedto=None):
        self.errid = errid
        if tiedto:
            self.tiedto = tiedto

    def add_distfix(self, distfix):
        if self.distfixes is None:
            self.distfixes = {}
        self.distfixes[(distfix.distid, distfix.bitness)] = distfix

    ### Instance validation and dumping

    def validate(self):
        """Checks that all the mandatory fields have been set for this fix.

        Note that this only checks internal consistency, not consistency with
        other fixes.
        """
        if not self.errid:
            raise AttributeError("CXFix.errid is not set")
        if not self.distfixes:
            raise AttributeError("No actual fixes are present for CXFix %s" % self.errid)
        for distfix in self.distfixes.values():
            distfix.validate(self.errid)
        return True

    def dump(self, out, indent=""):
        _dump_fields(out, indent, self, ('errid', 'tiedto', 'distfixes'))


#####
#
# Global validation checks
#
#####

def validate(distributions, fixes):
    errors = []

    checked = set()
    for distid, distribution in distributions.items():
        if distid in checked:
            continue
        if distribution.fallback:
            # Check for missing distributions and fallback loops
            seen = set((distid,))
            check = distribution
            while check.fallback:
                if check.fallback in seen:
                    errors.append("%s introduces a loop in the fallbacks of %s" % (check.distid, distid))
                    break
                elif check.fallback in distributions:
                    seen.add(check.distid)
                    check = distributions[check.fallback]
                else:
                    errors.append("The %s distribution has a nonexistent fallback: %s" % (check.distid, check.fallback))
                    break
            checked.update(seen)

    for fix in fixes.values():
        for errid in fix.tiedto:
            if errid not in fixes:
                errors.append("The %s fix is tied to a nonexistent error: %s" % (fix.errid, errid))
        for (distid, _bitness) in fix.distfixes.keys():
            if distid not in distributions:
                errors.append("The %s error has a fix for a nonexistent distribution: %s" % (fix.errid, distid))

    return errors