def generate_restrict_from_range(self, node, negate=False): op = str(node.get("range").strip()) slot = str(node.get("slot", "").strip()) try: restrict = self.op_translate[op.lstrip("r")] except KeyError: raise ValueError(f'unknown operator: {op!r}') if node.text is None: raise ValueError(f"{op!r} node missing version") base = str(node.text.strip()) glob = base.endswith("*") if glob: base = base[:-1] base = cpv.VersionedCPV(f"cat/pkg-{base}") if glob: if op != "eq": raise ValueError(f"glob cannot be used with {op} ops") return packages.PackageRestriction( "fullver", values.StrGlobMatch(base.fullver)) restrictions = [] if op.startswith("r"): if not base.revision: if op == "rlt": # rlt -r0 can never match # this is a non-range. raise ValueError( "range %s version %s is a guaranteed empty set" % (op, str(node.text.strip()))) elif op == "rle": # rle -r0 -> = -r0 return atom_restricts.VersionMatch("=", base.version, negate=negate) elif op == "rge": # rge -r0 -> ~ return atom_restricts.VersionMatch("~", base.version, negate=negate) # rgt -r0 passes through to regular ~ + > restrictions.append(atom_restricts.VersionMatch("~", base.version)) restrictions.append( atom_restricts.VersionMatch(restrict, base.version, rev=base.revision), ) if slot: restrictions.append(atom_restricts.SlotDep(slot)) return packages.AndRestriction(*restrictions, negate=negate)
def test_selected_targets(self, fakerepo): # selected repo options, _func = self.tool.parse_args(self.args + ['-r', 'stubrepo']) assert options.target_repo.repo_id == 'stubrepo' assert options.restrictions == [(base.repository_scope, packages.AlwaysTrue)] # dir path options, _func = self.tool.parse_args(self.args + [fakerepo]) assert options.target_repo.repo_id == 'fakerepo' assert options.restrictions == [(base.repository_scope, packages.AlwaysTrue)] # file path os.makedirs(pjoin(fakerepo, 'dev-util', 'foo')) ebuild_path = pjoin(fakerepo, 'dev-util', 'foo', 'foo-0.ebuild') touch(ebuild_path) options, _func = self.tool.parse_args(self.args + [ebuild_path]) restrictions = [ restricts.CategoryDep('dev-util'), restricts.PackageDep('foo'), restricts.VersionMatch('=', '0'), ] assert list(options.restrictions) == [ (base.version_scope, packages.AndRestriction(*restrictions)) ] assert options.target_repo.repo_id == 'fakerepo' # cwd path in unconfigured repo with chdir(pjoin(fakerepo, 'dev-util', 'foo')): options, _func = self.tool.parse_args(self.args) assert options.target_repo.repo_id == 'fakerepo' restrictions = [ restricts.CategoryDep('dev-util'), restricts.PackageDep('foo'), ] assert list(options.restrictions) == [ (base.package_scope, packages.AndRestriction(*restrictions)) ] # cwd path in configured repo stubrepo = pjoin(pkgcore_const.DATA_PATH, 'stubrepo') with chdir(stubrepo): options, _func = self.tool.parse_args(self.args) assert options.target_repo.repo_id == 'stubrepo' assert list(options.restrictions) == [(base.repository_scope, packages.AlwaysTrue)]
def path_restrict(self, path): """Return a restriction from a given path in a repo. :param path: full or partial path to an ebuild :return: a package restriction matching the given path if possible :raises ValueError: if the repo doesn't contain the given path, the path relates to a file that isn't an ebuild, or the ebuild isn't in the proper directory layout """ if path not in self: raise ValueError( f"{self.repo_id!r} repo doesn't contain: {path!r}") if not path.startswith(os.sep) and os.path.exists( pjoin(self.location, path)): path_chunks = path.split(os.path.sep) else: path = os.path.realpath(os.path.abspath(path)) relpath = path[len(os.path.realpath(self.location)):].strip('/') path_chunks = relpath.split(os.path.sep) if os.path.isfile(path): if not path.endswith('.ebuild'): raise ValueError(f"file is not an ebuild: {path!r}") elif len(path_chunks) != 3: # ebuild isn't in a category/PN directory raise ValueError( f"ebuild not in the correct directory layout: {path!r}") restrictions = [] # add restrictions until path components run out try: restrictions.append(restricts.RepositoryDep(self.repo_id)) if path_chunks[0] in self.categories: restrictions.append(restricts.CategoryDep(path_chunks[0])) restrictions.append(restricts.PackageDep(path_chunks[1])) base = cpv.VersionedCPV( f"{path_chunks[0]}/{os.path.splitext(path_chunks[2])[0]}") restrictions.append( restricts.VersionMatch('=', base.version, rev=base.revision)) except IndexError: pass return packages.AndRestriction(*restrictions)
def native__getattr__(self, attr): if attr != "restrictions": raise AttributeError(attr) # 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)) r = tuple(r) object.__setattr__(self, attr, r) return r
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 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 then other does not # match anything smaller than its own fullver: if other.revision is not None: return False # If other.revision is None 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')