diff --git a/README.rst b/README.rst index adde47078..9f883ed8c 100644 --- a/README.rst +++ b/README.rst @@ -267,8 +267,8 @@ First, compile ``requirements.txt`` as usual: # # pip-compile # - django==2.1.12 - pytz==2019.2 # via django + django==2.1.15 + pytz==2019.3 # via django Now compile the dev requirements and the ``requirements.txt`` file is used as a constraint: @@ -282,8 +282,9 @@ a constraint: # # pip-compile dev-requirements.in # - django-debug-toolbar==2.0 - django==2.1.12 # via django-debug-toolbar + django-debug-toolbar==2.1 + django==2.1.15 # via django-debug-toolbar + pytz==2019.3 # via django sqlparse==0.3.0 # via django-debug-toolbar As you can see above, even though a ``2.2`` release of Django is available, the diff --git a/piptools/resolver.py b/piptools/resolver.py index 7473d4bd6..88631c263 100644 --- a/piptools/resolver.py +++ b/piptools/resolver.py @@ -332,6 +332,17 @@ def _iter_dependencies(self, ireq): Editable requirements will never be looked up, as they may have changed at any time. """ + # Pip does not resolve dependencies of constraints. We skip handling + # constraints here as well to prevent the cache from being polluted. + # Constraints that are later determined to be dependencies will be + # marked as non-constraints in later rounds by + # `combine_install_requirements`, and will be properly resolved. + # See https://github.com/pypa/pip/ + # blob/6896dfcd831330c13e076a74624d95fa55ff53f4/src/pip/_internal/ + # legacy_resolve.py#L325 + if ireq.constraint: + return + if ireq.editable or is_url_requirement(ireq): for dependency in self.repository.get_dependencies(ireq): yield dependency diff --git a/tests/test_cli_compile.py b/tests/test_cli_compile.py index 0cf552283..fd6ba7b0f 100644 --- a/tests/test_cli_compile.py +++ b/tests/test_cli_compile.py @@ -1016,3 +1016,27 @@ def test_remove_outdated_options(runner, input_opts, output_opts): assert out.exit_code == 0, out assert out.stderr.strip() == input_opts + + +def test_sub_dependencies_with_constraints(pip_conf, runner): + # Write constraints file + with open("constraints.txt", "w") as constraints_in: + constraints_in.write("small-fake-a==0.1\n") + constraints_in.write("small-fake-b==0.2\n") + constraints_in.write("small-fake-with-unpinned-deps==0.1") + + with open("requirements.in", "w") as req_in: + req_in.write("-c constraints.txt\n") + req_in.write("small_fake_with_deps_and_sub_deps") # require fake package + + out = runner.invoke(cli) + + assert out.exit_code == 0 + + req_out_lines = set(out.stderr.splitlines()) + assert { + "small-fake-a==0.1 # via small-fake-with-unpinned-deps", + "small-fake-b==0.2 # via small-fake-with-unpinned-deps", + "small-fake-with-deps-and-sub-deps==0.1", + "small-fake-with-unpinned-deps==0.1 # via small-fake-with-deps-and-sub-deps", + }.issubset(req_out_lines) diff --git a/tests/test_data/fake-index.json b/tests/test_data/fake-index.json index df64009ef..5c5d76677 100644 --- a/tests/test_data/fake-index.json +++ b/tests/test_data/fake-index.json @@ -1,4 +1,7 @@ { + "aiohttp": { + "3.6.2": {"": ["yarl"]} + }, "anyjson": { "0.3.3": {"": []} }, @@ -79,6 +82,9 @@ "setuptools>=18.5" ]} }, + "idna": { + "2.8": {"": []} + }, "ipython": { "2.1.0": { "": ["gnureadline"], @@ -163,5 +169,8 @@ "0.6": {"": []}, "0.10": {"": []}, "0.10.4": {"": []} + }, + "yarl": { + "1.4.2": {"": ["idna"]} } } diff --git a/tests/test_data/minimal_wheels/small_fake_with_deps_and_sub_deps-0.1-py2.py3-none-any.whl b/tests/test_data/minimal_wheels/small_fake_with_deps_and_sub_deps-0.1-py2.py3-none-any.whl new file mode 100644 index 000000000..d611277c0 Binary files /dev/null and b/tests/test_data/minimal_wheels/small_fake_with_deps_and_sub_deps-0.1-py2.py3-none-any.whl differ diff --git a/tests/test_data/packages/small_fake_with_deps_and_sub_deps/setup.py b/tests/test_data/packages/small_fake_with_deps_and_sub_deps/setup.py new file mode 100644 index 000000000..bff3d6bac --- /dev/null +++ b/tests/test_data/packages/small_fake_with_deps_and_sub_deps/setup.py @@ -0,0 +1,7 @@ +from setuptools import setup + +setup( + name="small_fake_with_deps_and_sub_deps", + version=0.1, + install_requires=["small-fake-with-unpinned-deps"], +) diff --git a/tests/test_resolver.py b/tests/test_resolver.py index 97792ac4c..91cd5248a 100644 --- a/tests/test_resolver.py +++ b/tests/test_resolver.py @@ -172,6 +172,11 @@ "pytz==2016.4 (from celery==4.0.2)", ], ), + # Check that dependencies of relevant constraints are resolved + ( + ["aiohttp", ("yarl==1.4.2", True)], + ["aiohttp==3.6.2", "idna==2.8 (from yarl==1.4.2)", "yarl==1.4.2"], + ), ] ), ) @@ -253,6 +258,19 @@ def test_iter_dependencies(resolver, from_line): next(res._iter_dependencies(ireq)) +def test_iter_dependencies_results(resolver, from_line): + res = resolver([]) + ireq = from_line("aiohttp==3.6.2") + assert next(res._iter_dependencies(ireq)).comes_from == ireq + + +def test_iter_dependencies_ignores_constraints(resolver, from_line): + res = resolver([]) + ireq = from_line("aiohttp==3.6.2", constraint=True) + with pytest.raises(StopIteration): + next(res._iter_dependencies(ireq)) + + def test_combine_install_requirements(from_line): celery30 = from_line("celery>3.0", comes_from="-r requirements.in") celery31 = from_line("celery==3.1.1", comes_from=from_line("fake-package"))