class Subproject: """Data on a subproject. :type inherit_members: C{bool} :ivar inherit_members: whether the parent project inherits members from this subproject """ __slots__ = ('_ref', 'inherit_members', '_projects_xml', '_project') def __init__(self, ref, projects_xml, inherit_members=None): if ref is None: raise ValueError('ref for subproject must not be null') self._ref = ref self.inherit_members = inherit_members self._projects_xml = projects_xml @klass.jit_attr def project(self): try: return self._projects_xml.projects[self._ref] except KeyError: logger.error( f'projects.xml: subproject {self._ref!r} does not exist') return None __getattr__ = klass.GetAttrProxy('project') __dir__ = klass.DirProxy('project')
class wrapper(base): __slots__ = ("_raw_pkg", ) klass.inject_richcmp_methods_from_cmp(locals()) def operations(self, domain, **kwds): return self._raw_pkg._operations(domain, self, **kwds) def __init__(self, raw_pkg): object.__setattr__(self, "_raw_pkg", raw_pkg) def __cmp__(self, other): if isinstance(other, wrapper): return cmp(self._raw_pkg, other._raw_pkg) return cmp(self._raw_pkg, other) def __eq__(self, other): if isinstance(other, wrapper): return cmp(self._raw_pkg, other._raw_pkg) == 0 return cmp(self._raw_pkg, other) == 0 def __ne__(self, other): return not self == other __getattr__ = klass.GetAttrProxy("_raw_pkg") built = klass.alias_attr("_raw_pkg.built") versioned_atom = klass.alias_attr("_raw_pkg.versioned_atom") unversioned_atom = klass.alias_attr("_raw_pkg.unversioned_atom") is_supported = klass.alias_attr('_raw_pkg.is_supported') def __hash__(self): return hash(self._raw_pkg)
class FakeConfigurable: "Package wrapper binding profile data.""" configurable = True __slots__ = ('use', 'iuse', '_forced_use', '_masked_use', '_pkg_use', '_raw_pkg', '_profile') def __init__(self, pkg, profile): object.__setattr__(self, '_raw_pkg', pkg) object.__setattr__(self, '_profile', profile) object.__setattr__( self, '_forced_use', self._profile.forced_use.pull_data(self._raw_pkg)) object.__setattr__( self, '_masked_use', self._profile.masked_use.pull_data(self._raw_pkg)) object.__setattr__( self, '_pkg_use', self._profile.pkg_use.pull_data(self._raw_pkg)) use_defaults = {x[1:] for x in pkg.iuse if x[0] == '+'} enabled_use = (use_defaults | profile.use | self._pkg_use | self._forced_use) - self._masked_use object.__setattr__( self, 'use', frozenset(enabled_use & (profile.iuse_effective | pkg.iuse_effective))) object.__setattr__( self, 'iuse', frozenset(profile.iuse_effective.union(pkg.iuse_stripped))) def request_enable(self, attr, *vals): if attr != 'use': return False set_vals = frozenset(vals) if not set_vals.issubset(self.iuse): # requested a flag that doesn't exist in iuse return False # if any of the flags are in masked_use, it's a no go. return set_vals.isdisjoint(self._masked_use) def request_disable(self, attr, *vals): if attr != 'use': return False set_vals = frozenset(vals) if not set_vals.issubset(self.iuse): # requested a flag that doesn't exist in iuse return False # if any of the flags are forced_use, it's a no go. return set_vals.isdisjoint(self._forced_use) def rollback(self, point=0): return True def changes_count(self): return 0 def __str__(self): return str(self._raw_pkg) __getattr__ = klass.GetAttrProxy("_raw_pkg") def __setattr__(self, attr, val): raise AttributeError(self, 'is immutable')
class FakeConfigurable(object): configurable = True __slots__ = ('use', 'iuse', '_forced_use', '_masked_use', '_raw_pkg', '_profile') def __init__(self, pkg, profile): object.__setattr__(self, '_raw_pkg', pkg) object.__setattr__(self, '_profile', profile) object.__setattr__(self, '_forced_use', self._profile.forced_use.pull_data(self._raw_pkg)) object.__setattr__(self, '_masked_use', self._profile.masked_use.pull_data(self._raw_pkg)) enabled_use = self._forced_use - self._masked_use object.__setattr__(self, 'use', frozenset(enabled_use & profile.iuse_effective)) object.__setattr__( self, 'iuse', frozenset(pkg.iuse_stripped | profile.iuse_effective)) def request_enable(self, attr, *vals): if attr != 'use': return False set_vals = frozenset(vals) if not set_vals.issubset(self.iuse): # requested a flag that doesn't exist in iuse return False # if any of the flags are in masked_use, it's a no go. return set_vals.isdisjoint(self._masked_use) def request_disable(self, attr, *vals): if attr != 'use': return False set_vals = frozenset(vals) if not set_vals.issubset(self.iuse): # requested a flag that doesn't exist in iuse return False # if any of the flags are forced_use, it's a no go. return set_vals.isdisjoint(self._forced_use) def rollback(self, point=0): return True def changes_count(self): return 0 __getattr__ = klass.GetAttrProxy("_raw_pkg") def __setattr__(self, attr, val): raise AttributeError(self, 'is immutable')
class AtomicWriteFile(AtomicWriteFile_mixin): __doc__ = AtomicWriteFile_mixin.__doc__ def _actual_init(self): self.raw = io.open(self._temp_fp, mode=self._computed_mode) def _real_close(self): if hasattr(self, 'raw'): return self.raw.close() return None __getattr__ = klass.GetAttrProxy("raw")
class wrapper(base): __slots__ = ("_raw_pkg", "_domain") def operations(self, domain, **kwds): return self._raw_pkg._operations(domain, self, **kwds) def __init__(self, raw_pkg): object.__setattr__(self, "_raw_pkg", raw_pkg) def __eq__(self, other): if isinstance(other, wrapper): return self._raw_pkg == other._raw_pkg try: return self._raw_pkg == other except TypeError: return False def __ne__(self, other): return not self.__eq__(other) def __lt__(self, other): if isinstance(other, wrapper): return self._raw_pkg < other._raw_pkg return self._raw_pkg < other def __le__(self, other): return self.__lt__(other) or self.__eq__(other) def __gt__(self, other): if isinstance(other, wrapper): return self._raw_pkg > other._raw_pkg return self._raw_pkg > other def __ge__(self, other): return self.__gt__(other) or self.__eq__(other) __getattr__ = klass.GetAttrProxy("_raw_pkg") __dir__ = klass.DirProxy("_raw_pkg") _get_attr = klass.alias_attr("_raw_pkg._get_attr") built = klass.alias_attr("_raw_pkg.built") versioned_atom = klass.alias_attr("_raw_pkg.versioned_atom") unversioned_atom = klass.alias_attr("_raw_pkg.unversioned_atom") is_supported = klass.alias_attr('_raw_pkg.is_supported') def __hash__(self): return hash(self._raw_pkg)
class WrappedPkg: """Generic package wrapper used to inject attributes into package objects.""" __slots__ = ('_pkg', ) def __init__(self, pkg): self._pkg = pkg def __str__(self): return str(self._pkg) def __repr__(self): return repr(self._pkg) def __lt__(self, other): return self.versioned_atom < other.versioned_atom __getattr__ = klass.GetAttrProxy('_pkg') __dir__ = klass.DirProxy('_pkg')
class EbuildRepo: """Class for creating/manipulating ebuild repos.""" def __init__(self, path, repo_id='fake', eapi='5', masters=(), arches=()): self.path = path self.arches = _FileSet(pjoin(self.path, 'profiles', 'arch.list')) self._today = datetime.today() try: os.makedirs(pjoin(path, 'profiles')) with open(pjoin(path, 'profiles', 'repo_name'), 'w') as f: f.write(f'{repo_id}\n') with open(pjoin(path, 'profiles', 'eapi'), 'w') as f: f.write(f'{eapi}\n') os.makedirs(pjoin(path, 'metadata')) with open(pjoin(path, 'metadata', 'layout.conf'), 'w') as f: f.write(textwrap.dedent(f"""\ masters = {' '.join(masters)} cache-formats = thin-manifests = true """)) if arches: self.arches.update(arches) os.makedirs(pjoin(path, 'eclass')) except FileExistsError: pass self.sync() def sync(self): """Forcibly create underlying repo object avoiding cache usage.""" # avoid issues loading modules that set signal handlers from pkgcore.ebuild import repo_objs, repository repo_config = repo_objs.RepoConfig(location=self.path, disable_inst_caching=True) self._repo = repository.UnconfiguredTree(self.path, repo_config=repo_config) def create_profiles(self, profiles): for p in profiles: os.makedirs(pjoin(self.path, 'profiles', p.path), exist_ok=True) with open(pjoin(self.path, 'profiles', 'profiles.desc'), 'a+') as f: f.write(f'{p.arch} {p.path} {p.status}\n') if p.deprecated: with open(pjoin(self.path, 'profiles', p.path, 'deprecated'), 'w') as f: f.write("# deprecated\ndeprecation reason\n") with open(pjoin(self.path, 'profiles', p.path, 'make.defaults'), 'w') as f: if p.defaults is not None: f.write('\n'.join(p.defaults)) else: f.write(f'ARCH={p.arch}\n') if p.eapi: with open(pjoin(self.path, 'profiles', p.path, 'eapi'), 'w') as f: f.write(f'{p.eapi}\n') def create_ebuild(self, cpvstr, data=None, **kwargs): from pkgcore.ebuild import cpv as cpv_mod cpv = cpv_mod.VersionedCPV(cpvstr) self._repo.notify_add_package(cpv) ebuild_dir = pjoin(self.path, cpv.category, cpv.package) os.makedirs(ebuild_dir, exist_ok=True) # use defaults for some ebuild metadata if unset eapi = kwargs.pop('eapi', '7') slot = kwargs.pop('slot', '0') desc = kwargs.pop('description', 'stub package description') homepage = kwargs.pop('homepage', 'https://github.com/pkgcore/pkgcheck') license = kwargs.pop('license', 'blank') ebuild_path = pjoin(ebuild_dir, f'{cpv.package}-{cpv.fullver}.ebuild') with open(ebuild_path, 'w') as f: if self.repo_id == 'gentoo': f.write(textwrap.dedent(f"""\ # Copyright 1999-{self._today.year} Gentoo Authors # Distributed under the terms of the GNU General Public License v2 """)) f.write(f'EAPI="{eapi}"\n') f.write(f'DESCRIPTION="{desc}"\n') f.write(f'HOMEPAGE="{homepage}"\n') f.write(f'SLOT="{slot}"\n') if license: f.write(f'LICENSE="{license}"\n') # create a fake license os.makedirs(pjoin(self.path, 'licenses'), exist_ok=True) touch(pjoin(self.path, 'licenses', license)) for k, v in kwargs.items(): # handle sequences such as KEYWORDS and IUSE if isinstance(v, (tuple, list)): v = ' '.join(v) f.write(f'{k.upper()}="{v}"\n') if data: f.write(data.strip() + '\n') return ebuild_path def __iter__(self): yield from iter(self._repo) __getattr__ = klass.GetAttrProxy('_repo') __dir__ = klass.DirProxy('_repo')
class Cache: """Mixin for data caches.""" __getattr__ = klass.GetAttrProxy('_cache')
class atom(boolean.AndRestriction, metaclass=klass.generic_equality): """Currently implements gentoo ebuild atom parsing. Should be converted into an agnostic dependency base. """ # note we don't need _hash __slots__ = ( "blocks", "blocks_strongly", "op", "cpvstr", "negate_vers", "use", "slot_operator", "slot", "subslot", "repo_id", "_hash", "_cpv", "_restrictions", ) type = restriction.package_type negate = False _evaluate_collapse = True __attr_comparison__ = ("cpvstr", "op", "blocks", "negate_vers", "use", "slot", "subslot", "slot_operator", "repo_id") klass.inject_richcmp_methods_from_cmp(locals()) # hack; combine these 2 metaclasses at some point... locals().pop("__eq__", None) locals().pop("__ne__", None) __inst_caching__ = True # overrided in child class if it's supported evaluate_depset = None def __init__(self, atom, negate_vers=False, eapi='-1'): """ :param atom: string, see gentoo ebuild atom syntax :keyword negate_vers: boolean controlling whether the version should be inverted for restriction matching :keyword eapi: string/int controlling what eapi to enforce for this atom """ if not atom: raise errors.MalformedAtom(atom) sf = object.__setattr__ orig_atom = atom override_kls = False use_start = atom.find("[") slot_start = atom.find(":") if use_start != -1: # use dep use_end = atom.find("]", use_start) if use_end == -1: raise errors.MalformedAtom(orig_atom, "use restriction isn't completed") elif use_end != len(atom) - 1: raise errors.MalformedAtom(orig_atom, "trailing garbage after use dep") sf(self, "use", tuple(sorted(atom[use_start + 1:use_end].split(',')))) for x in self.use: # stripped purely for validation reasons try: if x[-1] in "=?": override_kls = True x = x[:-1] if x[0] == '!': x = x[1:] if x[0] == '-': raise errors.MalformedAtom( orig_atom, f"malformed use flag: {x}") elif x[0] == '-': x = x[1:] if x[-1] == ')' and eapi not in ('0', '1', '2', '3'): # use defaults. if x[-3:] in ("(+)", "(-)"): x = x[:-3] if not x: raise errors.MalformedAtom(orig_atom, 'empty use dep detected') if not valid_use_flag.match(x): raise errors.MalformedAtom(orig_atom, f'invalid USE flag: {x!r}') except IndexError: raise errors.MalformedAtom(orig_atom, 'empty use dep detected') if override_kls: sf(self, '__class__', transitive_use_atom) atom = atom[0:use_start] + atom[use_end + 1:] else: sf(self, "use", None) if slot_start != -1: i2 = atom.find("::", slot_start) if i2 != -1: repo_id = atom[i2 + 2:] if not repo_id: raise errors.MalformedAtom(orig_atom, "repo_id must not be empty") elif repo_id[0] in '-': raise errors.MalformedAtom( orig_atom, f"invalid first char of repo_id '{repo_id}' (must not begin with a hyphen)" ) elif not valid_repo_chars.issuperset(repo_id): raise errors.MalformedAtom( orig_atom, f"repo_id may contain only [a-Z0-9_-/], found {repo_id!r}" ) atom = atom[:i2] sf(self, "repo_id", repo_id) else: sf(self, "repo_id", None) # slot dep. slot = atom[slot_start + 1:] slot_operator = subslot = None if not slot: # if the slot char came in only due to repo_id, force slots to None if i2 == -1: raise errors.MalformedAtom( orig_atom, "Empty slot targets aren't allowed") slot = None else: slots = (slot, ) if eapi not in ('0', '1', '2', '3', '4'): if slot[0:1] in ("*", "="): if len(slot) > 1: raise errors.MalformedAtom( orig_atom, "Slot operators '*' and '=' do not take slot targets" ) slot_operator = slot slot, slots = None, () else: if slot.endswith('='): slot_operator = '=' slot = slot[:-1] slots = slot.split('/', 1) elif eapi == '0': raise errors.MalformedAtom( orig_atom, "slot dependencies aren't allowed in EAPI 0") for chunk in slots: if not chunk: raise errors.MalformedAtom( orig_atom, "Empty slot targets aren't allowed") if chunk[0] in '-.': raise errors.MalformedAtom( orig_atom, "Slot targets must not start with a hypen or dot: {chunk!r}" ) elif not valid_slot_chars.issuperset(chunk): invalid_chars = ', '.join( map( repr, sorted( set(chunk).difference(valid_slot_chars)))) raise errors.MalformedAtom( orig_atom, f"Invalid character(s) in slot target: {invalid_chars}" ) if len(slots) == 2: slot, subslot = slots sf(self, "slot_operator", slot_operator) sf(self, "slot", slot) sf(self, "subslot", subslot) atom = atom[:slot_start] else: sf(self, "slot_operator", None) sf(self, "slot", None) sf(self, "subslot", None) sf(self, "repo_id", None) sf(self, "blocks", atom[0] == "!") if self.blocks: atom = atom[1:] # hackish/slow, but lstrip doesn't take a 'prune this many' arg # open to alternatives if eapi not in ('0', '1') and atom.startswith("!"): atom = atom[1:] sf(self, "blocks_strongly", True) else: sf(self, "blocks_strongly", False) else: sf(self, "blocks_strongly", False) if atom[0] in ('<', '>'): if atom[1] == '=': sf(self, 'op', atom[:2]) atom = atom[2:] else: sf(self, 'op', atom[0]) atom = atom[1:] elif atom[0] == '=': if atom[-1] == '*': sf(self, 'op', '=*') atom = atom[1:-1] else: atom = atom[1:] sf(self, 'op', '=') elif atom[0] == '~': sf(self, 'op', '~') atom = atom[1:] else: sf(self, 'op', '') sf(self, 'cpvstr', atom) if eapi == '0': for x in ('use', 'slot'): if getattr(self, x) is not None: raise errors.MalformedAtom( orig_atom, f"{x} atoms aren't supported for EAPI 0") elif eapi == '1': if self.use is not None: raise errors.MalformedAtom( orig_atom, "use atoms aren't supported for EAPI < 2") if eapi != '-1': if self.repo_id is not None: raise errors.MalformedAtom( orig_atom, f"repo_id atoms aren't supported for EAPI {eapi}") if use_start != -1 and slot_start != -1 and use_start < slot_start: raise errors.MalformedAtom(orig_atom, "slot restriction must proceed use") try: sf(self, "_cpv", cpv.CPV(self.cpvstr, versioned=bool(self.op))) except errors.InvalidCPV as e: raise errors.MalformedAtom(orig_atom) from e if self.op: if self.version is None: raise errors.MalformedAtom(orig_atom, "operator requires a version") elif self.op == '~' and self.revision: raise errors.MalformedAtom( orig_atom, "~ revision operater cannot be combined with a revision") elif self.version is not None: raise errors.MalformedAtom(orig_atom, 'versioned atom requires an operator') sf(self, "_hash", hash(orig_atom)) sf(self, "negate_vers", negate_vers) __getattr__ = klass.GetAttrProxy("_cpv") __dir__ = klass.DirProxy("_cpv") @property def blocks_temp_ignorable(self): return not self.blocks_strongly weak_blocker = klass.alias_attr("blocks_temp_ignorable") def __repr__(self): if self.op == '=*': atom = f"={self.cpvstr}*" else: atom = self.op + self.cpvstr if self.blocks: atom = '!' + atom if self.blocks: if self.blocks_strongly: atom = '!!' + atom else: atom = '!' + atom attrs = [atom] if self.use: attrs.append(f'use={self.use!r}') if self.slot is not None: attrs.append(f'slot={self.slot!r}') if self.subslot is not None: attrs.append(f'subslot={self.subslot!r}') if self.repo_id is not None: attrs.append(f'repo_id={self.repo_id!r}') return '<%s %s @#%x>' % (self.__class__.__name__, ' '.join(attrs), id(self)) def __reduce__(self): return (atom, (str(self), self.negate_vers)) def iter_dnf_solutions(self, full_solution_expansion=False): if full_solution_expansion: return boolean.AndRestriction.iter_dnf_solutions(self, True) return iter([[self]]) def iter_cnf_solutions(self, full_solution_expansion=False): if full_solution_expansion: return boolean.AndRestriction.iter_cnf_solutions(self, True) return iter([[self]]) def cnf_solutions(self, full_solution_expansion=False): if full_solution_expansion: return boolean.AndRestriction.cnf_solutions(self, True) return [[self]] @property def is_simple(self): return len(self.restrictions) == 2 @klass.jit_attr def restrictions(self): # ordering here matters; against 24702 ebuilds for # a non matchable atom with package as the first restriction # 10 loops, best of 3: 206 msec per loop # with category as the first(the obvious ordering) # 10 loops, best of 3: 209 msec per loop # why? because category is more likely to collide; # at the time of this profiling, there were 151 categories. # over 11k packages however. r = [ restricts.PackageDep(self.package), restricts.CategoryDep(self.category) ] if self.repo_id is not None: r.insert(0, restricts.RepositoryDep(self.repo_id)) if self.fullver is not None: if self.op == '=*': r.append( packages.PackageRestriction( "fullver", values.StrGlobMatch(self.fullver))) else: r.append( restricts.VersionMatch(self.op, self.version, self.revision, negate=self.negate_vers)) if self.slot is not None: r.append(restricts.SlotDep(self.slot)) if self.subslot is not None: r.append(restricts.SubSlotDep(self.subslot)) if self.use is not None: r.extend(restricts._parse_nontransitive_use(self.use)) return tuple(r) def __str__(self): if self.op == '=*': s = f"={self.cpvstr}*" else: s = self.op + self.cpvstr if self.blocks: if self.blocks_strongly: s = '!!' + s else: s = '!' + s if self.slot: s += f":{self.slot}" if self.subslot: s += f"/{self.subslot}" if self.slot_operator == "=": s += self.slot_operator elif self.slot_operator: s += f":{self.slot_operator}" if self.repo_id: s += f"::{self.repo_id}" if self.use: use = ','.join(self.use) s += f"[{use}]" return s __hash__ = klass.reflective_hash('_hash') def __iter__(self): return iter(self.restrictions) def __getitem__(self, index): return self.restrictions[index] def __cmp__(self, other): if not isinstance(other, atom): raise TypeError( f"other isn't of {atom!r} type, is {other.__class__}") c = cmp(self.category, other.category) if c: return c c = cmp(self.package, other.package) if c: return c c = cmp(self.op, other.op) if c: return c c = cpv.ver_cmp(self.version, self.revision, other.version, other.revision) if c: return c c = cmp(self.blocks, other.blocks) if c: # invert it; cmp(True, False) == 1 # want non blockers then blockers. return -c c = cmp(self.blocks_strongly, other.blocks_strongly) if c: # want !! prior to ! return c c = cmp(self.negate_vers, other.negate_vers) if c: return c def f(v): return '' if v is None else v c = cmp(f(self.slot), f(other.slot)) if c: return c c = cmp(self.use, other.use) if c: return c return cmp(self.repo_id, other.repo_id) no_usedeps = klass.alias_attr("get_atom_without_use_deps") @property def get_atom_without_use_deps(self): """Return atom object stripped of USE dependencies.""" if not self.use: return self if self.op == '=*': s = f'={self.cpvstr}*' else: s = self.op + self.cpvstr if self.blocks: s = '!' + s if not self.blocks_temp_ignorable: s = '!' + s if self.slot: s += f':{self.slot}' return atom(s) def intersects(self, other): """Check if a passed in atom "intersects" this restriction's atom. Two atoms "intersect" if a package can be constructed that matches both: - if you query for just "dev-lang/python" it "intersects" both "dev-lang/python" and ">=dev-lang/python-2.4" - if you query for "=dev-lang/python-2.4" it "intersects" ">=dev-lang/python-2.4" and "dev-lang/python" but not "<dev-lang/python-2.3" USE and slot deps are also taken into account. The block/nonblock state of the atom is ignored. """ # Our "key" (cat/pkg) must match exactly: if self.key != other.key: return False # Slot dep only matters if we both have one. If we do they # must be identical: if (self.slot is not None and other.slot is not None and self.slot != other.slot): return False # Subslot dep only matters if we both have one. If we do they # must be identical: if (self.subslot is not None and other.subslot is not None and self.subslot != other.subslot): return False if (self.repo_id is not None and other.repo_id is not None and self.repo_id != other.repo_id): return False # Use deps are similar: if one of us forces a flag on and the # other forces it off we do not intersect. If only one of us # cares about a flag it is irrelevant. # Skip the (very common) case of one of us not having use deps: if self.use and other.use: # Set of flags we do not have in common: flags = set(self.use) ^ set(other.use) for flag in flags: # If this is unset and we also have the set version we fail: if flag[0] == '-' and flag[1:] in flags: return False # Remaining thing to check is version restrictions. Get the # ones we can check without actual version comparisons out of # the way first. # If one of us is unversioned we intersect: if not self.op or not other.op: return True # If we are both "unbounded" in the same direction we intersect: if (('<' in self.op and '<' in other.op) or ('>' in self.op and '>' in other.op)): return True # Trick used here: just use the atoms as sufficiently # package-like object to pass to these functions (all that is # needed is a version and revision attr). # If one of us is an exact match we intersect if the other matches it: if self.op == '=': if other.op == '=*': return self.fullver.startswith(other.fullver) return restricts.VersionMatch(other.op, other.version, other.revision).match(self) if other.op == '=': if self.op == '=*': return other.fullver.startswith(self.fullver) return restricts.VersionMatch(self.op, self.version, self.revision).match(other) # If we are both ~ matches we match if we are identical: if self.op == other.op == '~': return (self.version == other.version and self.revision == other.revision) # If we are both glob matches we match if one of us matches the other. if self.op == other.op == '=*': return (self.fullver.startswith(other.fullver) or other.fullver.startswith(self.fullver)) # If one of us is a glob match and the other a ~ we match if the glob # matches the ~ (ignoring a revision on the glob): if self.op == '=*' and other.op == '~': return other.fullver.startswith(self.version) if other.op == '=*' and self.op == '~': return self.fullver.startswith(other.version) # If we get here at least one of us is a <, <=, > or >=: if self.op in ('<', '<=', '>', '>='): ranged, other = self, other else: ranged, other = other, self if '<' in other.op or '>' in other.op: # We are both ranged, and in the opposite "direction" (or # we would have matched above). We intersect if we both # match the other's endpoint (just checking one endpoint # is not enough, it would give a false positive on <=2 vs >2) return (restricts.VersionMatch(other.op, other.version, other.revision).match(ranged) and restricts.VersionMatch(ranged.op, ranged.version, ranged.revision).match(other)) if other.op == '~': # Other definitely matches its own version. If ranged also # does we're done: if restricts.VersionMatch(ranged.op, ranged.version, ranged.revision).match(other): return True # The only other case where we intersect is if ranged is a # > or >= on other's version and a nonzero revision. In # that case other will match ranged. Be careful not to # give a false positive for ~2 vs <2 here: return ranged.op in ('>', '>=') and restricts.VersionMatch( other.op, other.version, other.revision).match(ranged) if other.op == '=*': # The fun one, since glob matches do not correspond to a # single contiguous region of versions. # a glob match definitely matches its own version, so if # ranged does too we're done: if restricts.VersionMatch(ranged.op, ranged.version, ranged.revision).match(other): return True if '<' in ranged.op: # Remaining cases where this intersects: there is a # package smaller than ranged.fullver and # other.fullver that they both match. # If other.revision is not None or 0 then other does not match # anything smaller than its own fullver: if other.revision: return False # If other.revision is None or 0 then we can always # construct a package smaller than other.fullver by # tagging e.g. an _alpha1 on, since # cat/pkg_beta2_alpha1_alpha1 is a valid version. # (Yes, really. Try it if you don't believe me.) # If and only if other also matches ranged then # ranged will also match one of those smaller packages. # XXX (I think, need to try harder to verify this.) return ranged.fullver.startswith(other.version) else: # Remaining cases where this intersects: there is a # package greater than ranged.fullver and # other.fullver that they both match. # We can always construct a package greater than # other.fullver by adding a digit to it. # If and only if other also matches ranged then # ranged will match such a larger package # XXX (I think, need to try harder to verify this.) return ranged.fullver.startswith(other.version) # Handled all possible ops. raise NotImplementedError( 'Someone added an op to atom without adding it to intersects') def evaluate_conditionals(self, parent_cls, parent_seq, enabled, tristate=None): parent_seq.append(self)