What did you want to do?
pip install --use-feature 2020-resolver sphinx==1.8.5
pip install --use-feature 2020-resolver sphinx sphinxcontrib-bibtex
After installing sphinx 1.8.5, pip freeze gives this:
alabaster==0.7.12
Babel==2.8.0
certifi==2020.6.20
chardet==3.0.4
docutils==0.16
idna==2.10
imagesize==1.2.0
Jinja2==2.11.2
MarkupSafe==1.1.1
packaging==20.4
Pygments==2.7.1
pyparsing==2.4.7
pytz==2020.1
requests==2.24.0
six==1.15.0
snowballstemmer==2.0.0
Sphinx==1.8.5
sphinxcontrib-serializinghtml==1.1.4
sphinxcontrib-websupport==1.2.4
urllib3==1.25.11
Output
ERROR: Exception:
Traceback (most recent call last):
File "/home/jahn/usr/src/pipmaster/src/pip/_internal/cli/base_command.py", line 222, in _main
status = self.run(options, args)
File "/home/jahn/usr/src/pipmaster/src/pip/_internal/cli/req_command.py", line 178, in wrapper
return func(self, options, args)
File "/home/jahn/usr/src/pipmaster/src/pip/_internal/commands/install.py", line 378, in run
requirement_set
File "/home/jahn/usr/src/pipmaster/src/pip/_internal/resolution/resolvelib/resolver.py", line 183, in get_installation_order
weights = get_topological_weights(graph)
File "/home/jahn/usr/src/pipmaster/src/pip/_internal/resolution/resolvelib/resolver.py", line 242, in get_topological_weights
assert len(weights) == len(graph)
AssertionError
Additional information
Dependency tree (before error):
pipdeptree==1.0.0
- pip [required: >=6.0.0, installed: 20.3.dev0]
Sphinx==1.8.5
- alabaster [required: >=0.7,<0.8, installed: 0.7.12]
- babel [required: >=1.3,!=2.0, installed: 2.8.0]
- pytz [required: >=2015.7, installed: 2020.1]
- docutils [required: >=0.11, installed: 0.16]
- imagesize [required: Any, installed: 1.2.0]
- Jinja2 [required: >=2.3, installed: 2.11.2]
- MarkupSafe [required: >=0.23, installed: 1.1.1]
- packaging [required: Any, installed: 20.4]
- pyparsing [required: >=2.0.2, installed: 2.4.7]
- six [required: Any, installed: 1.15.0]
- Pygments [required: >=2.0, installed: 2.7.1]
- requests [required: >=2.0.0, installed: 2.24.0]
- certifi [required: >=2017.4.17, installed: 2020.6.20]
- chardet [required: >=3.0.2,<4, installed: 3.0.4]
- idna [required: >=2.5,<3, installed: 2.10]
- urllib3 [required: >=1.21.1,<1.26,!=1.25.1,!=1.25.0, installed: 1.25.11]
- setuptools [required: Any, installed: 41.6.0]
- six [required: >=1.5, installed: 1.15.0]
- snowballstemmer [required: >=1.1, installed: 2.0.0]
- sphinxcontrib-websupport [required: Any, installed: 1.2.4]
- sphinxcontrib-serializinghtml [required: Any, installed: 1.1.4]
When installing sphinx and sphixcontrib-bibtex, sphinx is set to upgrade to 3.2.1. The error only appears when sphinx is given on the command line. When only sphinxcontrib-bibtex is installed, it works fine and sphinx is upgraded. The error appears both in 20.2.4 and master.
This is the value of weights
in get_topological_weights:
{'six': 5, '<Python from Requires-Python>': 5, 'latexcodec': 4, 'pyyaml': 4, 'pybtex': 3, 'setuptools': 3, 'oset': 2, 'docutils': 3, 'pybtex-docutils': 2, 'pyparsing': 4, 'packaging': 3, 'pygments': 3, 'alabaster': 3, 'sphinxcontrib-devhelp': 3, 'sphinxcontrib-jsmath': 3, 'sphinxcontrib-serializinghtml': 3, 'markupsafe': 4, 'jinja2': 3, 'snowballstemmer': 3, 'sphinxcontrib-applehelp': 3, 'sphinxcontrib-htmlhelp': 3, 'idna': 4, 'chardet': 4, 'urllib3': 4, 'certifi': 4, 'requests': 3, 'pytz': 4, 'babel': 3, 'imagesize': 3, 'sphinxcontrib-qthelp': 3, 'sphinx': 2, 'sphinxcontrib-bibtex': 1, None: 0}
This is graph
:
<pip._vendor.resolvelib.structs.DirectedGraph object at 0x7f99ccef19d0>
This is graph._vertices
:
{'<Python from Requires-Python>', 'oset', None, 'idna', 'sphinxcontrib-devhelp', 'sphinxcontrib-jsmath', 'urllib3', 'sphinxcontrib-htmlhelp', 'six', 'imagesize', 'pyyaml', 'snowballstemmer', 'certifi', 'sphinxcontrib-qthelp', 'markupsafe', 'docutils', 'packaging', 'sphinxcontrib-bibtex', 'sphinx', 'pytz', 'alabaster', 'pyparsing', 'jinja2', 'sphinxcontrib-applehelp', 'babel', 'pygments', 'pybtex-docutils', 'setuptools', 'sphinxcontrib-websupport', 'sphinxcontrib-serializinghtml', 'pybtex', 'requests', 'latexcodec', 'chardet'}
sphinxcontrib-websupport is missing from weights.
This is graph._forwards
:
{None: {'sphinxcontrib-bibtex', 'sphinx'}, 'sphinx': {'docutils', 'packaging', '<Python from Requires-Python>', 'pygments', 'setuptools', 'alabaster', 'sphinxcontrib-devhelp', 'sphinxcontrib-jsmath', 'sphinxcontrib-serializinghtml', 'jinja2', 'snowballstemmer', 'sphinxcontrib-applehelp', 'sphinxcontrib-htmlhelp', 'requests', 'babel', 'imagesize', 'sphinxcontrib-qthelp'}, 'sphinxcontrib-bibtex': {'pybtex', 'oset', 'pybtex-docutils', 'sphinx'}, 'alabaster': set(), 'babel': {'pytz'}, 'six': set(), 'packaging': {'six', 'pyparsing'}, 'pybtex': {'six', 'latexcodec', 'pyyaml', '<Python from Requires-Python>'}, 'pybtex-docutils': {'six', 'docutils', 'pybtex', '<Python from Requires-Python>'}, 'latexcodec': {'six', '<Python from Requires-Python>'}, 'docutils': set(), 'jinja2': {'markupsafe'}, 'imagesize': set(), 'requests': {'idna', 'chardet', 'urllib3', 'certifi'}, 'setuptools': set(), 'oset': {'setuptools'}, 'snowballstemmer': set(), 'pygments': set(), 'sphinxcontrib-jsmath': {'<Python from Requires-Python>'}, 'sphinxcontrib-applehelp': {'<Python from Requires-Python>'}, 'sphinxcontrib-htmlhelp': {'<Python from Requires-Python>'}, 'sphinxcontrib-qthelp': {'<Python from Requires-Python>'}, 'sphinxcontrib-devhelp': {'<Python from Requires-Python>'}, 'sphinxcontrib-serializinghtml': set(), 'sphinxcontrib-websupport': {'sphinxcontrib-serializinghtml'}, '<Python from Requires-Python>': set(), 'pyyaml': {'<Python from Requires-Python>'}, 'pytz': set(), 'markupsafe': set(), 'urllib3': set(), 'certifi': set(), 'chardet': set(), 'idna': set(), 'pyparsing': set()}
This is graph._backwards
:
{None: set(), 'sphinx': {'sphinxcontrib-bibtex', None}, 'sphinxcontrib-bibtex': {None}, 'alabaster': {'sphinx'}, 'babel': {'sphinx'}, 'six': {'pybtex', 'packaging', 'latexcodec', 'pybtex-docutils'}, 'packaging': {'sphinx'}, 'pybtex': {'sphinxcontrib-bibtex', 'pybtex-docutils'}, 'pybtex-docutils': {'sphinxcontrib-bibtex'}, 'latexcodec': {'pybtex'}, 'docutils': {'pybtex-docutils', 'sphinx'}, 'jinja2': {'sphinx'}, 'imagesize': {'sphinx'}, 'requests': {'sphinx'}, 'setuptools': {'oset', 'sphinx'}, 'oset': {'sphinxcontrib-bibtex'}, 'snowballstemmer': {'sphinx'}, 'pygments': {'sphinx'}, 'sphinxcontrib-jsmath': {'sphinx'}, 'sphinxcontrib-applehelp': {'sphinx'}, 'sphinxcontrib-htmlhelp': {'sphinx'}, 'sphinxcontrib-qthelp': {'sphinx'}, 'sphinxcontrib-devhelp': {'sphinx'}, 'sphinxcontrib-serializinghtml': {'sphinxcontrib-websupport', 'sphinx'}, 'sphinxcontrib-websupport': set(), '<Python from Requires-Python>': {'pybtex-docutils', 'sphinx', 'sphinxcontrib-devhelp', 'sphinxcontrib-jsmath', 'sphinxcontrib-applehelp', 'pybtex', 'sphinxcontrib-htmlhelp', 'latexcodec', 'pyyaml', 'sphinxcontrib-qthelp'}, 'pyyaml': {'pybtex'}, 'pytz': {'babel'}, 'markupsafe': {'jinja2'}, 'urllib3': {'requests'}, 'certifi': {'requests'}, 'chardet': {'requests'}, 'idna': {'requests'}, 'pyparsing': {'packaging'}}
This is causing my rtd builds to fail (https://readthedocs.org/projects/dclab/builds/12162768/). My workaround was to pin sphinx to sphinx<2
in requirements.txt. But this is hardly a good solution.
Thanks @jahn for a detailed report here!
It has been super helpful to have the reproducer as well as useful debugging information for this issue.
Visualising the graph here, it looks like we're getting a malformed graph from resolvelib.
Graph on 20.2.3 (good):
Graph on 20.2.4 (naughty):
Notice how the node for "sphinxcontrib-websupport" has sneaked into this one? That's causing the failure here.
A quick dump of words to describe things:
sphinxcontrib-websupport
.sphinxcontrib-websupport
)sphinxcontrib-websupport -> sphinxcontrib-serializinghtml
dependency.sphinxcontrib-websupport
here) since its "parent" (the older sphinx version) is no longer part of the result (and hence, the graph).sphinxcontrib-websupport
node during that chunk of code -- only nodes accessible from the root node). :)It seems to me that this issue arises because that we're adding nodes that have no "path to root" in the graph, by adding the parent unconditionally. IOW, this is a bug in resolvelib's _build_result
function, which means we'd need to fix it there (/cc @uranusjr).
SOOOO. Possible choices to make here:
Things to do here:
# This was the blob of code that I used to generate the input to graphviz, for those graphs.
print('digraph G {')
print(' rankdir=LR;')
for node, others in graph._forwards.items():
print(f' "{node}";')
for node, others in graph._forwards.items():
for to_node in others:
print(f' "{node}" -> "{to_node}";')
print('}')
/cc @pfmoore because I couldn't find a smooth excuse to mention you earlier than this line. :)
I figured that we're only seeing the installed version of sphinx
because of #8924 and because the user has specified sphinx
directly. Sooo... dropping sphinx from the set of packages that the user requests would mean that the resolver won't see the already-installed version.
And, yes, that works. Yay!
pip install --use-feature 2020-resolver sphinx==1.8.5
pip install --use-feature 2020-resolver sphinxcontrib-bibtex
Now, it's almost 4am. I'mma sleep.
Nice analysis! I think dropping sphinx
from the list of user-requested packages is a 100% acceptable workaround.
It would be nice to not have that node in the graph, but I don't feel that's a critical issue as we know it's not a genuine issue with the resolution or ordering. I'm not 100% clear whether that's possible solely in resolvelib or whether it would need some help from pip, but that's something we can work out.
Maybe change the assertion to a warning, so we don't completely lose the information that something odd is going on. And maybe if we can create a test to demonstrate the issue (make it xfail until we deal with it) that would also help us not to forget about it.
Brain dump over.
Oh, and @jahn can I add my thanks for a very well written, detailed and helpful issue report. It made it much easier to understand what's going on here.
I'm not 100% clear whether that's possible solely in resolvelib or whether it would need some help from pip, but that's something we can work out.
This is definitely solvable purely on the resolvelib side -- we'd basically need to rework the graph construction slightly to do the right thing.
Can you check whether sphinxcontrib-websupport
also end up in mapping
? resolvelib already does cleanup on it, and I’d be very worried if the cleanup is not working. If not, pip should be able to work around this by changing the sanity check to
assert len(weights) == len(result.mapping) + 1 # Plus the santinel None node.
instead. I will find some time to also fix it on resolvelib’s side as well, assuming the mapping
entries are correct.
Darn, I’m having a hard time trying to come up with an simplified example. It seems that a simple backtrack is not enough to cause the issue. This is what I was trying to use as a test case:
* tea-0 depends on kettle==0
* coffee-0 depends on kettle==0
* coffee-1 depends on kettle==1 and water==0
* water-0 depends on nothing
* kettle-0 does not depend on anything
* kettle-1 depends on non stove==0 which does not exist (causing a backtrack)
Now I instruct the resolver to resolve coffee before tea. This would induce backtracking because kettle-1
ultimately hits a dead end. water-0
was added for coffee-1
, and I was expecting it to be left behind after backtracking—but it was not. The resolver correctly cleans it up after resolution, and the graph only contains coffee-0
, tea-0
, and kettle-0
.
Not sure what I’m missing 😞
I thought a key point with the original example was that sphinxcontrib-websupport
was already installed even though we were upgrading to a version of sphinx
that didn't depend on it? Would that show up differently in the resolvelib data? It would mean that the older version of sphinx
that depended on sphinx-webcontrib
gets prioritised (and seen by the resolver) ahead of the later version that doesn't...
I persumed ordering is key as well, which is why I made the newer version of coffee
depend on water, instead of the older. In the original case, and older version of sphinx
being already installed means that the version is tried before the newer; in my made-up case, the newer kettle
is tried first.
I also tried mimicking the already installed behaviour by swapping kettle
versions:
* tea-0 depends on kettle==0
* coffee-0 depends on kettle==0
* coffee-1 depends on kettle==1 and water==0
* water-0 depends on nothing
* kettle-0 depends on non stove==0 which does not exist (causing a backtrack)
* kettle-1 does not depend on anything
and instruct the provider to put kettle-0
before kettle-1
(simulating version 0 being already installed). The behaviour is the same, water
is not present in the final graph—which is expected, since the resolver does not have any logic regarding the actual version, only how the candidates are ordered; the candidate ordering does not change between my two test cases.
Here’s the test code I’m using, maybe it’s me implementing things wrong…
def test_result_cleanup():
dependencies = {
("kettle", 0): [],
("kettle", 1): [("stove", {})],
("water", 0): [],
("tea", 0): [("kettle", {0})],
("coffee", 0): [("water", {0}), ("kettle", {1})],
("coffee", 1): [("kettle", {0})],
}
pref_vers = {"coffee": {0: 1}} # Prefer coffee-0 over coffee-1.
class Provider(AbstractProvider):
def identify(self, dependency):
return dependency[0]
def get_preference(self, resolution, candidates, information):
# Resolve coffee before tea. This will cause backtracking.
if candidates[0][0] == "tea":
return 1
return 0
def get_dependencies(self, candidate):
return dependencies[candidate]
def find_matches(self, requirements):
if not requirements:
return []
name, versions = requirements.pop()
versions = {
version
for version in versions
if all(version in r[1] for r in requirements)
}
# Prefer coffee-0 over coffee-1.
if name == "coffee":
return sorted((name, v) for v in versions)
return sorted(((name, v) for v in versions), reverse=True)
def is_satisfied_by(self, requirement, candidate):
return candidate[1] in requirement[1]
resolver = Resolver(Provider(), BaseReporter())
result = resolver.resolve([("coffee", {0, 1}), ("tea", {0})])
assert "water" in result.graph, list(result.graph)
# AssertionError: [None, 'kettle', 'tea', 'coffee']
Ah I see, sorry.
But in the actual example, sphinxcontrib-websupport
1.2.4 (the equivalent of your "stove") does exist, it's installed in the first run.
pip should be able to work around this by changing the sanity check to
assert len(weights) == len(result.mapping) + 1 # Plus the santinel None node.
Yup -- this works. :)
I just stumbled on this error; I see it's fixed already in 20.3 (and could confirm that it works for my use case in 20.3b1) - but while waiting for 20.3 to drop, what can I do to figure out what packages I need to lock/remove in my project as a workaround?
There is a 20.3b1 you can install.
The package that is already installed in the environment that would be updated. For ReadTheDocs builds, that's gonna be Sphinx.
@uranusjr Yes - I know (as stated, I've already tested that my use case works on 20.3b1); my point was that I might not be able to do this in the production environment, so I would like to know what I could do with my deps while waiting for a non-beta version.