Keep install options in requirements.txt from leaking

The list of install options passed into the setup routine is mutable,
passed by reference, so adding items for "this package" to that list
mutates the options for all subsequent packages.

Isolate the lists before mutating them.

Includes a functional test, which has been confirmed to fail without
this fix.

Fixes #3763
Fixes #4453
Fixes #5089
This commit is contained in:
Phil Pennock 2018-03-19 20:30:09 -04:00
parent 8dbdb74a6d
commit 23cd8f6899
3 changed files with 40 additions and 3 deletions

1
news/3763.bugfix Normal file
View File

@ -0,0 +1 @@
Keep install options in requirements.txt from leaking.

View File

@ -771,11 +771,13 @@ class InstallRequirement(object):
# Options specified in requirements file override those
# specified on the command line, since the last option given
# to setup.py is the one that is used.
global_options += self.options.get('global_options', [])
install_options += self.options.get('install_options', [])
global_options = list(global_options) + \
self.options.get('global_options', [])
install_options = list(install_options) + \
self.options.get('install_options', [])
if self.isolated:
global_options = list(global_options) + ["--no-user-cfg"]
global_options = global_options + ["--no-user-cfg"]
with TempDirectory(kind="record") as temp_dir:
record_filename = os.path.join(temp_dir.path, 'install-record.txt')

View File

@ -513,3 +513,37 @@ def test_install_unsupported_wheel_file(script, data):
assert ("simple.dist-0.1-py1-none-invalid.whl is not a supported " +
"wheel on this platform" in result.stderr)
assert len(result.files_created) == 0
def test_install_options_local_to_package(script, data, virtualenv):
"""Make sure --install-options does not leak across packages.
A requirements.txt file can have per-package --install-options; these
should be isolated to just the package instead of leaking to subsequent
packages. This needs to be a functional test because the bug was around
cross-contamination at install time.
"""
home_simple = script.scratch_path.join("for-simple")
test_simple = script.scratch.join("for-simple")
home_simple.mkdir()
reqs_file = script.scratch_path.join("reqs.txt")
reqs_file.write(
textwrap.dedent("""
simple --install-option='--home=%s'
INITools
""" % home_simple))
result = script.pip(
'install',
'--no-index', '-f', data.find_links,
'-r', reqs_file,
expect_error=True,
)
simple = test_simple / 'lib' / 'python' / 'simple'
bad = test_simple / 'lib' / 'python' / 'initools'
good = script.site_packages / 'initools'
assert simple in result.files_created
assert result.files_created[simple].dir
assert bad not in result.files_created
assert good in result.files_created
assert result.files_created[good].dir