Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion pip/req/req_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,8 @@ def add_requirement(self, install_req, parent_req_name=None):
except KeyError:
existing_req = None
if (parent_req_name is None and existing_req and not
existing_req.constraint):
existing_req.constraint and
existing_req.extras == install_req.extras):
raise InstallationError(
'Double requirement given: %s (already in %s, name=%r)'
% (install_req, existing_req, name))
Expand All @@ -267,6 +268,11 @@ def add_requirement(self, install_req, parent_req_name=None):
# If we're now installing a constraint, mark the existing
# object for real installation.
existing_req.constraint = False
existing_req.extras = tuple(
sorted(set(existing_req.extras).union(
set(install_req.extras))))
logger.debug("Setting %s extras to: %s" % (
existing_req, existing_req.extras))
# And now we need to scan this.
result = [existing_req]
# Canonicalise to the already-added object for the backref
Expand Down
1 change: 1 addition & 0 deletions tests/data/packages/LocalExtras-0.0.2/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/LocalExtras-0.0.2.egg-info
Empty file.
30 changes: 30 additions & 0 deletions tests/data/packages/LocalExtras-0.0.2/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import os
from setuptools import setup, find_packages


def path_to_url(path):
"""
Convert a path to URI. The path will be made absolute and
will not have quoted path parts.
"""
path = os.path.normpath(os.path.abspath(path))
drive, path = os.path.splitdrive(path)
filepath = path.split(os.path.sep)
url = '/'.join(filepath)
if drive:
return 'file:///' + drive + url
return 'file://' +url


HERE = os.path.dirname(__file__)
DEP_PATH = os.path.join(HERE, '..', '..', 'indexes', 'simple', 'simple')
DEP_URL = path_to_url(DEP_PATH)

setup(
name='LocalExtras',
version='0.0.2',
packages=find_packages(),
install_requires=['simple==1.0'],
extras_require={ 'bar': ['simple==2.0'], 'baz': ['singlemodule'] },
dependency_links=[DEP_URL]
)
2 changes: 1 addition & 1 deletion tests/data/packages/LocalExtras/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ def path_to_url(path):
name='LocalExtras',
version='0.0.1',
packages=find_packages(),
extras_require={ 'bar': ['simple'] },
extras_require={ 'bar': ['simple'], 'baz': ['singlemodule'] },
dependency_links=[DEP_URL]
)
92 changes: 92 additions & 0 deletions tests/functional/test_install_reqs.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,3 +343,95 @@ def test_double_install_spurious_hash_mismatch(script, tmpdir):
result = script.pip_install_local(
'-r', reqs_file.abspath, expect_error=False)
assert 'Successfully installed simple-1.0' in str(result)


def test_install_with_extras_from_constraints(script, data):
to_install = data.packages.join("LocalExtras")
script.scratch_path.join("constraints.txt").write(
"file://%s#egg=LocalExtras[bar]" % to_install
)
result = script.pip_install_local(
'-c', script.scratch_path / 'constraints.txt', 'LocalExtras')
assert script.site_packages / 'simple' in result.files_created


def test_install_with_extras_from_install(script, data):
to_install = data.packages.join("LocalExtras")
script.scratch_path.join("constraints.txt").write(
"file://%s#egg=LocalExtras" % to_install
)
result = script.pip_install_local(
'-c', script.scratch_path / 'constraints.txt', 'LocalExtras[baz]')
assert script.site_packages / 'singlemodule.py'in result.files_created


def test_install_with_extras_joined(script, data):
to_install = data.packages.join("LocalExtras")
script.scratch_path.join("constraints.txt").write(
"file://%s#egg=LocalExtras[bar]" % to_install
)
result = script.pip_install_local(
'-c', script.scratch_path / 'constraints.txt', 'LocalExtras[baz]'
)
assert script.site_packages / 'simple' in result.files_created
assert script.site_packages / 'singlemodule.py'in result.files_created


def test_install_with_extras_editable_joined(script, data):
to_install = data.packages.join("LocalExtras")
script.scratch_path.join("constraints.txt").write(
"-e file://%s#egg=LocalExtras[bar]" % to_install
)
result = script.pip_install_local(
'-c', script.scratch_path / 'constraints.txt', 'LocalExtras[baz]')
assert script.site_packages / 'simple' in result.files_created
assert script.site_packages / 'singlemodule.py'in result.files_created


def test_install_distribution_full_union(script, data):
to_install = data.packages.join("LocalExtras")
result = script.pip_install_local(
to_install, to_install + "[bar]", to_install + "[baz]")
assert 'Running setup.py install for LocalExtras' in result.stdout
assert script.site_packages / 'simple' in result.files_created
assert script.site_packages / 'singlemodule.py' in result.files_created


def test_install_distribution_duplicate_extras(script, data):
to_install = data.packages.join("LocalExtras")
package_name = to_install + "[bar]"
with pytest.raises(AssertionError):
result = script.pip_install_local(package_name, package_name)
assert 'Double requirement given: %s' % package_name in result.stderr


def test_install_distribution_union_with_constraints(script, data):
to_install = data.packages.join("LocalExtras")
script.scratch_path.join("constraints.txt").write(
"%s[bar]" % to_install)
result = script.pip_install_local(
'-c', script.scratch_path / 'constraints.txt', to_install + '[baz]')
assert 'Running setup.py install for LocalExtras' in result.stdout
assert script.site_packages / 'singlemodule.py' in result.files_created


def test_install_distribution_union_with_versions(script, data):
to_install_001 = data.packages.join("LocalExtras")
to_install_002 = data.packages.join("LocalExtras-0.0.2")
result = script.pip_install_local(
to_install_001 + "[bar]", to_install_002 + "[baz]")
assert ("Successfully installed LocalExtras-0.0.1 simple-3.0 " +
"singlemodule-0.0.1" in result.stdout)


@pytest.mark.xfail
def test_install_distribution_union_conflicting_extras(script, data):
# LocalExtras requires simple==1.0, LocalExtras[bar] requires simple==2.0;
# without a resolver, pip does not detect the conflict between simple==1.0
# and simple==2.0. Once a resolver is added, this conflict should be
# detected.
to_install = data.packages.join("LocalExtras-0.0.2")
result = script.pip_install_local(to_install, to_install + "[bar]",
expect_error=True)
assert 'installed' not in result.stdout
assert "Conflict" in result.stderr