This repository has been archived on 2025-09-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Alicja Cięciwa cb8886666c login page
2020-10-27 12:57:58 +01:00

170 lines
5.5 KiB
Python

# -*- coding=utf-8 -*-
from __future__ import absolute_import, unicode_literals
import copy
import itertools
import packaging.markers
import packaging.specifiers
import vistir
import vistir.misc
from ..internals.markers import get_without_extra
from ..internals.specifiers import cleanup_pyspecs, pyspec_from_markers
def dedup_markers(s):
# TODO: Implement better logic.
deduped = sorted(vistir.misc.dedup(s))
return deduped
class MetaSet(object):
"""Representation of a "metadata set".
This holds multiple metadata representaions. Each metadata representation
includes a marker, and a specifier set of Python versions required.
"""
def __init__(self):
self.markerset = frozenset()
self.pyspecset = packaging.specifiers.SpecifierSet()
def __repr__(self):
return "MetaSet(markerset={0!r}, pyspecset={1!r})".format(
",".join(sorted(self.markerset)), str(self.pyspecset),
)
def __str__(self):
pyspecs = set()
markerset = set()
for m in self.markerset:
marker_specs = pyspec_from_markers(packaging.markers.Marker(m))
if marker_specs:
pyspecs.add(marker_specs)
else:
markerset.add(m)
if pyspecs:
self.pyspecset._specs &= pyspecs
self.markerset = frozenset(markerset)
return " and ".join(dedup_markers(itertools.chain(
# Make sure to always use the same quotes so we can dedup properly.
(
"{0}".format(ms) if " or " in ms else ms
for ms in (str(m).replace('"', "'") for m in self.markerset)
),
(
"python_version {0[0]} '{0[1]}'".format(spec)
for spec in cleanup_pyspecs(self.pyspecset)
),
)))
def __bool__(self):
return bool(self.markerset or self.pyspecset)
def __nonzero__(self): # Python 2.
return self.__bool__()
def __or__(self, pair):
marker, specset = pair
markerset = set(self.markerset)
if marker:
marker_specs = pyspec_from_markers(marker)
if not marker_specs:
markerset.add(str(marker))
else:
specset._specs &= marker_specs
metaset = MetaSet()
metaset.markerset = frozenset(markerset)
# TODO: Implement some logic to clean up dups like '3.0.*' and '3.0'.
metaset.pyspecset &= self.pyspecset & specset
return metaset
def _build_metasets(dependencies, pythons, key, trace, all_metasets):
all_parent_metasets = []
for route in trace:
parent = route[-1]
try:
parent_metasets = all_metasets[parent]
except KeyError: # Parent not calculated yet. Wait for it.
return
all_parent_metasets.append((parent, parent_metasets))
metaset_iters = []
for parent, parent_metasets in all_parent_metasets:
r = dependencies[parent][key]
python = pythons[key]
metaset = (
get_without_extra(r.markers),
packaging.specifiers.SpecifierSet(python),
)
metaset_iters.append(
parent_metaset | metaset
for parent_metaset in parent_metasets
)
return list(itertools.chain.from_iterable(metaset_iters))
def _calculate_metasets_mapping(dependencies, pythons, traces):
all_metasets = {None: [MetaSet()]}
del traces[None]
while traces:
new_metasets = {}
for key, trace in traces.items():
assert key not in all_metasets, key # Sanity check for debug.
metasets = _build_metasets(
dependencies, pythons, key, trace, all_metasets,
)
if metasets is None:
continue
new_metasets[key] = metasets
if not new_metasets:
break # No progress? Deadlocked. Give up.
all_metasets.update(new_metasets)
for key in new_metasets:
del traces[key]
return all_metasets
def _format_metasets(metasets):
# If there is an unconditional route, this needs to be unconditional.
if not metasets or not all(metasets):
return None
# This extra str(Marker()) call helps simplify the expression.
return str(packaging.markers.Marker(" or ".join(
"{0}".format(s) if " and " in s else s
for s in dedup_markers(str(metaset) for metaset in metasets
if metaset)
)))
def set_metadata(candidates, traces, dependencies, pythons):
"""Add "metadata" to candidates based on the dependency tree.
Metadata for a candidate includes markers and a specifier for Python
version requirements.
:param candidates: A key-candidate mapping. Candidates in the mapping will
have their markers set.
:param traces: A graph trace (produced by `traces.trace_graph`) providing
information about dependency relationships between candidates.
:param dependencies: A key-collection mapping containing what dependencies
each candidate in `candidates` requested.
:param pythons: A key-str mapping containing Requires-Python information
of each candidate.
Keys in mappings and entries in the trace are identifiers of a package, as
implemented by the `identify` method of the resolver's provider.
The candidates are modified in-place.
"""
metasets_mapping = _calculate_metasets_mapping(
dependencies, pythons, copy.deepcopy(traces),
)
for key, candidate in candidates.items():
candidate.markers = _format_metasets(metasets_mapping[key])