Skip to content

Commit

Permalink
Merge pull request #11634 from q0w/per-req-config-settings
Browse files Browse the repository at this point in the history
  • Loading branch information
uranusjr authored Apr 10, 2023
2 parents 7cb863e + 110a26f commit 3ac7e10
Show file tree
Hide file tree
Showing 7 changed files with 166 additions and 13 deletions.
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 in requirements files.
3 changes: 3 additions & 0 deletions src/pip/_internal/cli/req_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
requirements.append(req_to_add)

Expand Down
3 changes: 3 additions & 0 deletions src/pip/_internal/req/constructors.py
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,7 @@ def install_req_from_parsed_requirement(
isolated: bool = False,
use_pep517: Optional[bool] = None,
user_supplied: bool = False,
config_settings: Optional[Dict[str, Union[str, List[str]]]] = None,
) -> InstallRequirement:
if parsed_req.is_editable:
req = install_req_from_editable(
Expand All @@ -462,6 +463,7 @@ def install_req_from_parsed_requirement(
constraint=parsed_req.constraint,
isolated=isolated,
user_supplied=user_supplied,
config_settings=config_settings,
)

else:
Expand All @@ -481,6 +483,7 @@ def install_req_from_parsed_requirement(
constraint=parsed_req.constraint,
line_source=parsed_req.line_source,
user_supplied=user_supplied,
config_settings=config_settings,
)
return req

Expand Down
1 change: 1 addition & 0 deletions src/pip/_internal/req/req_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
SUPPORTED_OPTIONS_REQ: List[Callable[..., optparse.Option]] = [
cmdoptions.global_options,
cmdoptions.hash,
cmdoptions.config_settings,
]

# the 'dest' string values
Expand Down
47 changes: 46 additions & 1 deletion tests/functional/test_config_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from zipfile import ZipFile

from pip._internal.utils.urls import path_to_url
from tests.lib import PipTestEnvironment
from tests.lib import PipTestEnvironment, create_basic_sdist_for_package

PYPROJECT_TOML = """\
[build-system]
Expand Down Expand Up @@ -123,6 +123,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(f"{name}-config.json")
assert json.loads(output) == {"FOO": "Hello"}


def test_backend_sees_config_via_constraint(script: PipTestEnvironment) -> None:
name, version, project_dir = make_project(script.scratch_path)
constraints_file = script.scratch_path / "constraints.txt"
Expand Down Expand Up @@ -244,6 +258,17 @@ def test_install_sees_config(script: PipTestEnvironment) -> None:
assert json.load(f) == {"FOO": "Hello"}


def test_install_sees_config_reqs(script: PipTestEnvironment) -> None:
name, _, 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 / f"{name}-config.json"
with open(config, "rb") as f:
assert json.load(f) == {"FOO": "Hello"}


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


def test_install_config_reqs(script: PipTestEnvironment) -> None:
name, _, 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(
f'{project_dir} --config-settings "--build-option=--cffi" '
'--config-settings "--build-option=--avx2" '
"--config-settings FOO=BAR"
)
script.pip("install", "--no-index", "-f", str(a_sdist.parent), "-r", "reqs.txt")
script.assert_installed(foo="1.0")
config = script.site_packages_path / f"{name}-config.json"
with open(config, "rb") as f:
assert json.load(f) == {"--build-option": ["--cffi", "--avx2"], "FOO": "BAR"}
110 changes: 100 additions & 10 deletions tests/functional/test_install_reqs.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import os
import textwrap
from pathlib import Path
from typing import Any, Callable
from typing import TYPE_CHECKING, Any

import pytest

Expand All @@ -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:
Expand All @@ -28,33 +33,42 @@ def args(self) -> Any:
return json.loads(self._args_path.read_text())


class ArgRecordingSdistMaker(Protocol):
def __call__(self, name: str, **kwargs: Any) -> ArgRecordingSdist:
...


@pytest.fixture()
def arg_recording_sdist_maker(
script: PipTestEnvironment,
) -> Callable[[str], ArgRecordingSdist]:
arg_writing_setup_py = textwrap.dedent(
) -> ArgRecordingSdistMaker:
arg_writing_setup_py_prelude = textwrap.dedent(
"""
import io
import json
import os
import sys
from setuptools import setup
args_path = os.path.join(os.environ["OUTPUT_DIR"], "{name}.json")
with open(args_path, 'w') as f:
json.dump(sys.argv, f)
setup(name={name!r}, version="0.1.0")
"""
)
output_dir = script.scratch_path.joinpath("args_recording_sdist_maker_output")
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,
**kwargs: Any,
) -> ArgRecordingSdist:
sdist_path = create_basic_sdist_for_package(
script,
name,
"0.1.0",
setup_py_prelude=arg_writing_setup_py_prelude.format(name=name),
**kwargs,
)
args_path = output_dir / f"{name}.json"
return ArgRecordingSdist(sdist_path, args_path)

Expand Down Expand Up @@ -727,3 +741,79 @@ def test_install_unsupported_wheel_file(
in result.stderr
)
assert len(result.files_created) == 0


def test_config_settings_local_to_package(
script: PipTestEnvironment,
common_wheels: Path,
arg_recording_sdist_maker: ArgRecordingSdistMaker,
) -> None:
pyproject_toml = textwrap.dedent(
"""
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
"""
)
simple0_sdist = arg_recording_sdist_maker(
"simple0",
extra_files={"pyproject.toml": pyproject_toml},
depends=["foo"],
)
foo_sdist = arg_recording_sdist_maker(
"foo",
extra_files={"pyproject.toml": pyproject_toml},
)
simple1_sdist = arg_recording_sdist_maker(
"simple1",
extra_files={"pyproject.toml": pyproject_toml},
depends=["bar"],
)
bar_sdist = arg_recording_sdist_maker(
"bar",
extra_files={"pyproject.toml": pyproject_toml},
depends=["simple3"],
)
simple3_sdist = arg_recording_sdist_maker(
"simple3", extra_files={"pyproject.toml": pyproject_toml}
)
simple2_sdist = arg_recording_sdist_maker(
"simple2",
extra_files={"pyproject.toml": pyproject_toml},
)

reqs_file = script.scratch_path.joinpath("reqs.txt")
reqs_file.write_text(
textwrap.dedent(
"""
simple0 --config-settings "--build-option=--verbose"
foo --config-settings "--build-option=--quiet"
simple1 --config-settings "--build-option=--verbose"
simple2
"""
)
)

script.pip(
"install",
"--no-index",
"-f",
script.scratch_path,
"-f",
common_wheels,
"-r",
reqs_file,
)

simple0_args = simple0_sdist.args()
assert "--verbose" in simple0_args
foo_args = foo_sdist.args()
assert "--quiet" in foo_args
simple1_args = simple1_sdist.args()
assert "--verbose" in simple1_args
bar_args = bar_sdist.args()
assert "--verbose" not in bar_args
simple3_args = simple3_sdist.args()
assert "--verbose" not in simple3_args
simple2_args = simple2_sdist.args()
assert "--verbose" not in simple2_args
14 changes: 12 additions & 2 deletions tests/unit/test_req_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,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:
Expand Down Expand Up @@ -343,10 +349,14 @@ def test_nested_constraints_file(
assert reqs[0].constraint

def test_options_on_a_requirement_line(self, line_processor: LineProcessor) -> None:
line = 'SomeProject --global-option="yo3" --global-option "yo4"'
line = (
'SomeProject --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.config_settings == {"yo3": "yo4", "yo1": "yo2"}

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

0 comments on commit 3ac7e10

Please sign in to comment.