Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add per-requirement --config-settings #11634

Merged
merged 26 commits into from
Apr 10, 2023
Merged
Changes from 4 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
1 change: 1 addition & 0 deletions news/11325.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Support a per-requirement ``--config-settings`` option.
uranusjr marked this conversation as resolved.
Show resolved Hide resolved
3 changes: 3 additions & 0 deletions src/pip/_internal/cli/req_command.py
Original file line number Diff line number Diff line change
@@ -438,6 +438,9 @@ def get_requirements(
isolated=options.isolated_mode,
use_pep517=options.use_pep517,
user_supplied=True,
config_settings=parsed_req.options.get("config_settings")
if parsed_req.options
else None,
uranusjr marked this conversation as resolved.
Show resolved Hide resolved
)
requirements.append(req_to_add)

1 change: 1 addition & 0 deletions src/pip/_internal/req/req_file.py
Original file line number Diff line number Diff line change
@@ -72,6 +72,7 @@
cmdoptions.install_options,
cmdoptions.global_options,
cmdoptions.hash,
cmdoptions.config_settings,
]

# the 'dest' string values
2 changes: 2 additions & 0 deletions src/pip/_internal/req/req_install.py
Original file line number Diff line number Diff line change
@@ -150,6 +150,8 @@ def __init__(
self.global_options = global_options if global_options else []
self.hash_options = hash_options if hash_options else {}
self.config_settings = config_settings
if isinstance(comes_from, InstallRequirement) and comes_from.config_settings:
q0w marked this conversation as resolved.
Show resolved Hide resolved
self.config_settings = comes_from.config_settings
q0w marked this conversation as resolved.
Show resolved Hide resolved
# Set to True after successful preparation of this requirement
self.prepared = False
# User supplied requirement are explicitly requested for installation
45 changes: 44 additions & 1 deletion tests/functional/test_config_settings.py
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@
from typing import Tuple
from zipfile import ZipFile

from tests.lib import PipTestEnvironment
from tests.lib import PipTestEnvironment, create_basic_sdist_for_package

PYPROJECT_TOML = """\
[build-system]
@@ -112,6 +112,20 @@ def test_backend_sees_config(script: PipTestEnvironment) -> None:
assert json.loads(output) == {"FOO": "Hello"}


def test_backend_sees_config_reqs(script: PipTestEnvironment) -> None:
name, version, project_dir = make_project(script.scratch_path)
script.scratch_path.joinpath("reqs.txt").write_text(
f"{project_dir} --config-settings FOO=Hello"
)
script.pip("wheel", "-r", "reqs.txt")
wheel_file_name = f"{name}-{version}-py3-none-any.whl"
wheel_file_path = script.cwd / wheel_file_name
with open(wheel_file_path, "rb") as f:
with ZipFile(f) as z:
output = z.read("config.json")
assert json.loads(output) == {"FOO": "Hello"}


def test_install_sees_config(script: PipTestEnvironment) -> None:
_, _, project_dir = make_project(script.scratch_path)
script.pip(
@@ -125,6 +139,17 @@ def test_install_sees_config(script: PipTestEnvironment) -> None:
assert json.load(f) == {"FOO": "Hello"}


def test_install_sees_config_reqs(script: PipTestEnvironment) -> None:
_, _, project_dir = make_project(script.scratch_path)
script.scratch_path.joinpath("reqs.txt").write_text(
f"{project_dir} --config-settings FOO=Hello"
)
script.pip("install", "-r", "reqs.txt")
config = script.site_packages_path / "config.json"
with open(config, "rb") as f:
assert json.load(f) == {"FOO": "Hello"}


def test_install_editable_sees_config(script: PipTestEnvironment) -> None:
_, _, project_dir = make_project(script.scratch_path)
script.pip(
@@ -137,3 +162,21 @@ def test_install_editable_sees_config(script: PipTestEnvironment) -> None:
config = script.site_packages_path / "config.json"
with open(config, "rb") as f:
assert json.load(f) == {"FOO": "Hello"}


def test_install_config_reqs(script: PipTestEnvironment) -> None:
_, _, project_dir = make_project(script.scratch_path)
a_sdist = create_basic_sdist_for_package(
script,
"foo",
"1.0",
{"pyproject.toml": PYPROJECT_TOML, "backend/dummy_backend.py": BACKEND_SRC},
)
script.scratch_path.joinpath("reqs.txt").write_text(
"foo --config-settings FOO=Hello"
)
script.pip("install", "--no-index", "-f", str(a_sdist.parent), "-r", "reqs.txt")
script.assert_installed(foo="1.0")
config = script.site_packages_path / "config.json"
with open(config, "rb") as f:
assert json.load(f) == {"FOO": "Hello"}
71 changes: 64 additions & 7 deletions tests/functional/test_install_reqs.py
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@
import os
import textwrap
from pathlib import Path
from typing import Any, Callable
from typing import TYPE_CHECKING, Any, Dict, Optional

import pytest

@@ -18,6 +18,11 @@
)
from tests.lib.local_repos import local_checkout

if TYPE_CHECKING:
from typing import Protocol
else:
Protocol = object


class ArgRecordingSdist:
def __init__(self, sdist_path: Path, args_path: Path) -> None:
@@ -28,10 +33,17 @@ def args(self) -> Any:
return json.loads(self._args_path.read_text())


class ArgRecordingSdistMaker(Protocol):
def __call__(
self, name: str, extra_files: Optional[Dict[str, str]] = None
) -> ArgRecordingSdist:
...


@pytest.fixture()
def arg_recording_sdist_maker(
script: PipTestEnvironment,
) -> Callable[[str], ArgRecordingSdist]:
) -> ArgRecordingSdistMaker:
arg_writing_setup_py = textwrap.dedent(
"""
import io
@@ -52,9 +64,13 @@ def arg_recording_sdist_maker(
output_dir.mkdir(parents=True)
script.environ["OUTPUT_DIR"] = str(output_dir)

def _arg_recording_sdist_maker(name: str) -> ArgRecordingSdist:
extra_files = {"setup.py": arg_writing_setup_py.format(name=name)}
sdist_path = create_basic_sdist_for_package(script, name, "0.1.0", extra_files)
def _arg_recording_sdist_maker(
name: str, extra_files: Optional[Dict[str, str]] = None
) -> ArgRecordingSdist:
_extra_files = {"setup.py": arg_writing_setup_py.format(name=name)}
if extra_files is not None:
_extra_files.update(extra_files)
sdist_path = create_basic_sdist_for_package(script, name, "0.1.0", _extra_files)
args_path = output_dir / f"{name}.json"
return ArgRecordingSdist(sdist_path, args_path)

@@ -334,7 +350,7 @@ def test_wheel_user_with_prefix_in_pydistutils_cfg(

def test_install_option_in_requirements_file_overrides_cli(
script: PipTestEnvironment,
arg_recording_sdist_maker: Callable[[str], ArgRecordingSdist],
arg_recording_sdist_maker: ArgRecordingSdistMaker,
) -> None:
simple_sdist = arg_recording_sdist_maker("simple")

@@ -763,7 +779,7 @@ def test_install_unsupported_wheel_file(

def test_install_options_local_to_package(
script: PipTestEnvironment,
arg_recording_sdist_maker: Callable[[str], ArgRecordingSdist],
arg_recording_sdist_maker: ArgRecordingSdistMaker,
) -> None:
"""Make sure --install-options does not leak across packages.

@@ -817,3 +833,44 @@ def test_location_related_install_option_fails(script: PipTestEnvironment) -> No
expect_error=True,
)
assert "['--home'] from simple" in result.stderr


@pytest.mark.network
def test_config_settings_local_to_package(
script: PipTestEnvironment, arg_recording_sdist_maker: ArgRecordingSdistMaker
) -> None:
pyproject_toml = textwrap.dedent(
"""
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
"""
)
simple1_sdist = arg_recording_sdist_maker(
"simple1", {"pyproject.toml": pyproject_toml}
)
simple2_sdist = arg_recording_sdist_maker(
"simple2", {"pyproject.toml": pyproject_toml}
)

reqs_file = script.scratch_path.joinpath("reqs.txt")
reqs_file.write_text(
textwrap.dedent(
"""
simple1 --config-settings "--build-option=--verbose"
simple2
"""
)
)
script.pip(
"install",
"-f",
str(simple1_sdist.sdist_path.parent),
"-r",
reqs_file,
)

simple1_args = simple1_sdist.args()
assert "--verbose" in simple1_args
simple2_args = simple2_sdist.args()
assert "--verbose" not in simple2_args
12 changes: 10 additions & 2 deletions tests/unit/test_req_file.py
Original file line number Diff line number Diff line change
@@ -74,7 +74,13 @@ def parse_reqfile(
options=options,
constraint=constraint,
):
yield install_req_from_parsed_requirement(parsed_req, isolated=isolated)
yield install_req_from_parsed_requirement(
parsed_req,
isolated=isolated,
config_settings=parsed_req.options.get("config_settings")
if parsed_req.options
else None,
)


def test_read_file_url(tmp_path: Path, session: PipSession) -> None:
@@ -346,12 +352,14 @@ def test_nested_constraints_file(
def test_options_on_a_requirement_line(self, line_processor: LineProcessor) -> None:
line = (
"SomeProject --install-option=yo1 --install-option yo2 "
'--global-option="yo3" --global-option "yo4"'
'--global-option="yo3" --global-option "yo4" '
'--config-settings="yo3=yo4" --config-settings "yo1=yo2"'
)
filename = "filename"
req = line_processor(line, filename, 1)[0]
assert req.global_options == ["yo3", "yo4"]
assert req.install_options == ["yo1", "yo2"]
assert req.config_settings == {"yo3": "yo4", "yo1": "yo2"}

def test_hash_options(self, line_processor: LineProcessor) -> None:
"""Test the --hash option: mostly its value storage.