Skip to content

Commit e950859

Browse files
authored
Merge pull request #8556 from chrahunt/maint/fail-on-install-location-options
Disallow explicitly passing install-location-related arguments in --install-options
2 parents 6679b5e + 8c7b942 commit e950859

File tree

4 files changed

+115
-71
lines changed

4 files changed

+115
-71
lines changed

news/7309.removal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Disallow passing install-location-related arguments in ``--install-options``.

src/pip/_internal/commands/install.py

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
from pip._internal.operations.check import check_install_conflicts
2222
from pip._internal.req import install_given_reqs
2323
from pip._internal.req.req_tracker import get_requirement_tracker
24-
from pip._internal.utils.deprecation import deprecated
2524
from pip._internal.utils.distutils_args import parse_distutils_args
2625
from pip._internal.utils.filesystem import test_writable_dir
2726
from pip._internal.utils.misc import (
@@ -290,7 +289,7 @@ def run(self, options, args):
290289
try:
291290
reqs = self.get_requirements(args, options, finder, session)
292291

293-
warn_deprecated_install_options(
292+
reject_location_related_install_options(
294293
reqs, options.install_options
295294
)
296295

@@ -606,7 +605,7 @@ def decide_user_install(
606605
return True
607606

608607

609-
def warn_deprecated_install_options(requirements, options):
608+
def reject_location_related_install_options(requirements, options):
610609
# type: (List[InstallRequirement], Optional[List[str]]) -> None
611610
"""If any location-changing --install-option arguments were passed for
612611
requirements or on the command-line, then show a deprecation warning.
@@ -639,20 +638,12 @@ def format_options(option_names):
639638
if not offenders:
640639
return
641640

642-
deprecated(
643-
reason=(
644-
"Location-changing options found in --install-option: {}. "
645-
"This configuration may cause unexpected behavior and is "
646-
"unsupported.".format(
647-
"; ".join(offenders)
648-
)
649-
),
650-
replacement=(
651-
"using pip-level options like --user, --prefix, --root, and "
652-
"--target"
653-
),
654-
gone_in="20.2",
655-
issue=7309,
641+
raise CommandError(
642+
"Location-changing options found in --install-option: {}."
643+
" This is unsupported, use pip-level options like --user,"
644+
" --prefix, --root, and --target instead.".format(
645+
"; ".join(offenders)
646+
)
656647
)
657648

658649

tests/functional/test_install_reqs.py

Lines changed: 93 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
import json
12
import os
23
import textwrap
34

45
import pytest
56

67
from tests.lib import (
78
_create_test_package_with_subdirectory,
9+
create_basic_sdist_for_package,
810
create_basic_wheel_for_package,
911
need_svn,
1012
path_to_url,
@@ -15,6 +17,51 @@
1517
from tests.lib.path import Path
1618

1719

20+
class ArgRecordingSdist(object):
21+
def __init__(self, sdist_path, args_path):
22+
self.sdist_path = sdist_path
23+
self._args_path = args_path
24+
25+
def args(self):
26+
return json.loads(self._args_path.read_text())
27+
28+
29+
@pytest.fixture()
30+
def arg_recording_sdist_maker(script):
31+
arg_writing_setup_py = textwrap.dedent(
32+
"""
33+
import io
34+
import json
35+
import os
36+
import sys
37+
38+
from setuptools import setup
39+
40+
args_path = os.path.join(os.environ["OUTPUT_DIR"], "{name}.json")
41+
with open(args_path, 'w') as f:
42+
json.dump(sys.argv, f)
43+
44+
setup(name={name!r}, version="0.1.0")
45+
"""
46+
)
47+
output_dir = script.scratch_path.joinpath(
48+
"args_recording_sdist_maker_output"
49+
)
50+
output_dir.mkdir(parents=True)
51+
script.environ["OUTPUT_DIR"] = str(output_dir)
52+
53+
def _arg_recording_sdist_maker(name):
54+
# type: (str) -> ArgRecordingSdist
55+
extra_files = {"setup.py": arg_writing_setup_py.format(name=name)}
56+
sdist_path = create_basic_sdist_for_package(
57+
script, name, "0.1.0", extra_files
58+
)
59+
args_path = output_dir / "{}.json".format(name)
60+
return ArgRecordingSdist(sdist_path, args_path)
61+
62+
return _arg_recording_sdist_maker
63+
64+
1865
@pytest.mark.network
1966
def test_requirements_file(script):
2067
"""
@@ -259,28 +306,21 @@ def test_wheel_user_with_prefix_in_pydistutils_cfg(
259306
assert 'installed requiresupper' in result.stdout
260307

261308

262-
def test_install_option_in_requirements_file(script, data, virtualenv):
263-
"""
264-
Test --install-option in requirements file overrides same option in cli
265-
"""
266-
267-
script.scratch_path.joinpath("home1").mkdir()
268-
script.scratch_path.joinpath("home2").mkdir()
269-
270-
script.scratch_path.joinpath("reqs.txt").write_text(
271-
textwrap.dedent(
272-
"""simple --install-option='--home={home}'""".format(
273-
home=script.scratch_path.joinpath("home1"))))
309+
def test_install_option_in_requirements_file_overrides_cli(
310+
script, arg_recording_sdist_maker
311+
):
312+
simple_sdist = arg_recording_sdist_maker("simple")
274313

275-
result = script.pip(
276-
'install', '--no-index', '-f', data.find_links, '-r',
277-
script.scratch_path / 'reqs.txt',
278-
'--install-option=--home={home}'.format(
279-
home=script.scratch_path.joinpath("home2")),
280-
expect_stderr=True)
314+
reqs_file = script.scratch_path.joinpath("reqs.txt")
315+
reqs_file.write_text("simple --install-option='-O0'")
281316

282-
package_dir = script.scratch / 'home1' / 'lib' / 'python' / 'simple'
283-
result.did_create(package_dir)
317+
script.pip(
318+
'install', '--no-index', '-f', str(simple_sdist.sdist_path.parent),
319+
'-r', str(reqs_file), '--install-option=-O1',
320+
)
321+
simple_args = simple_sdist.args()
322+
assert 'install' in simple_args
323+
assert simple_args.index('-O1') < simple_args.index('-O0')
284324

285325

286326
def test_constraints_not_installed_by_default(script, data):
@@ -605,35 +645,49 @@ def test_install_unsupported_wheel_file(script, data):
605645
assert len(result.files_created) == 0
606646

607647

608-
def test_install_options_local_to_package(script, data):
648+
def test_install_options_local_to_package(script, arg_recording_sdist_maker):
609649
"""Make sure --install-options does not leak across packages.
610650
611651
A requirements.txt file can have per-package --install-options; these
612652
should be isolated to just the package instead of leaking to subsequent
613653
packages. This needs to be a functional test because the bug was around
614654
cross-contamination at install time.
615655
"""
616-
home_simple = script.scratch_path.joinpath("for-simple")
617-
test_simple = script.scratch.joinpath("for-simple")
618-
home_simple.mkdir()
656+
657+
simple1_sdist = arg_recording_sdist_maker("simple1")
658+
simple2_sdist = arg_recording_sdist_maker("simple2")
659+
619660
reqs_file = script.scratch_path.joinpath("reqs.txt")
620661
reqs_file.write_text(
621-
textwrap.dedent("""
622-
simple --install-option='--home={home_simple}'
623-
INITools
624-
""".format(**locals())))
625-
result = script.pip(
662+
textwrap.dedent(
663+
"""
664+
simple1 --install-option='-O0'
665+
simple2
666+
"""
667+
)
668+
)
669+
script.pip(
626670
'install',
627-
'--no-index', '-f', data.find_links,
671+
'--no-index', '-f', str(simple1_sdist.sdist_path.parent),
628672
'-r', reqs_file,
629-
expect_stderr=True,
630673
)
631674

632-
simple = test_simple / 'lib' / 'python' / 'simple'
633-
bad = test_simple / 'lib' / 'python' / 'initools'
634-
good = script.site_packages / 'initools'
635-
result.did_create(simple)
636-
assert result.files_created[simple].dir
637-
result.did_not_create(bad)
638-
result.did_create(good)
639-
assert result.files_created[good].dir
675+
simple1_args = simple1_sdist.args()
676+
assert 'install' in simple1_args
677+
assert '-O0' in simple1_args
678+
simple2_args = simple2_sdist.args()
679+
assert 'install' in simple2_args
680+
assert '-O0' not in simple2_args
681+
682+
683+
def test_location_related_install_option_fails(script):
684+
simple_sdist = create_basic_sdist_for_package(script, "simple", "0.1.0")
685+
reqs_file = script.scratch_path.joinpath("reqs.txt")
686+
reqs_file.write_text("simple --install-option='--home=/tmp'")
687+
result = script.pip(
688+
'install',
689+
'--no-index', '-f', str(simple_sdist.parent),
690+
'-r', reqs_file,
691+
expect_error=True
692+
)
693+
assert "['--home'] from simple" in result.stderr

tests/unit/test_command_install.py

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@
77
from pip._internal.commands.install import (
88
create_env_error_message,
99
decide_user_install,
10-
warn_deprecated_install_options,
10+
reject_location_related_install_options,
1111
)
12+
from pip._internal.exceptions import CommandError
1213
from pip._internal.req.req_install import InstallRequirement
1314

1415

@@ -44,16 +45,15 @@ def test_most_cases(
4445
assert decide_user_install(use_user_site=None) is result
4546

4647

47-
def test_deprecation_notice_for_pip_install_options(recwarn):
48+
def test_rejection_for_pip_install_options():
4849
install_options = ["--prefix=/hello"]
49-
warn_deprecated_install_options([], install_options)
50+
with pytest.raises(CommandError) as e:
51+
reject_location_related_install_options([], install_options)
5052

51-
assert len(recwarn) == 1
52-
message = recwarn[0].message.args[0]
53-
assert "['--prefix'] from command line" in message
53+
assert "['--prefix'] from command line" in str(e.value)
5454

5555

56-
def test_deprecation_notice_for_requirement_options(recwarn):
56+
def test_rejection_for_location_requirement_options():
5757
install_options = []
5858

5959
bad_named_req_options = ["--home=/wow"]
@@ -67,18 +67,16 @@ def test_deprecation_notice_for_requirement_options(recwarn):
6767
None, "requirements2.txt", install_options=bad_unnamed_req_options
6868
)
6969

70-
warn_deprecated_install_options(
71-
[bad_named_req, bad_unnamed_req], install_options
72-
)
73-
74-
assert len(recwarn) == 1
75-
message = recwarn[0].message.args[0]
70+
with pytest.raises(CommandError) as e:
71+
reject_location_related_install_options(
72+
[bad_named_req, bad_unnamed_req], install_options
73+
)
7674

7775
assert (
7876
"['--install-lib'] from <InstallRequirement> (from requirements2.txt)"
79-
in message
77+
in str(e.value)
8078
)
81-
assert "['--home'] from hello (from requirements.txt)" in message
79+
assert "['--home'] from hello (from requirements.txt)" in str(e.value)
8280

8381

8482
@pytest.mark.parametrize('error, show_traceback, using_user_site, expected', [

0 commit comments

Comments
 (0)