diff --git a/docs/html/reference/build-system/pyproject-toml.md b/docs/html/reference/build-system/pyproject-toml.md index d2ec0323e6a..a42a3b8c484 100644 --- a/docs/html/reference/build-system/pyproject-toml.md +++ b/docs/html/reference/build-system/pyproject-toml.md @@ -116,6 +116,12 @@ multiple times, in order to specify multiple settings). The supplied configuration settings are passed to every backend hook call. +Configuration settings provided via `--config-settings` command line options (or the +equivalent environment variables or configuration file entries) are passed to the build +of requirements explicitly provided as pip command line arguments. They are not passed +to the build of dependencies, or to the build of requirements provided in requirement +files. + ## Build output It is the responsibility of the build backend to ensure that the output is diff --git a/news/11941.feature.rst b/news/11941.feature.rst new file mode 100644 index 00000000000..404f2cb2de6 --- /dev/null +++ b/news/11941.feature.rst @@ -0,0 +1,4 @@ +Stop propagating CLI ``--config-settings`` to the build dependencies. They already did +not propagate to requirements provided in requirement files. To pass the same config +settings to several requirements, users should provide the requirements as CLI +arguments. diff --git a/src/pip/_internal/cli/req_command.py b/src/pip/_internal/cli/req_command.py index bb33403195b..8c326013223 100644 --- a/src/pip/_internal/cli/req_command.py +++ b/src/pip/_internal/cli/req_command.py @@ -344,7 +344,6 @@ def make_resolver( install_req_from_req_string, isolated=options.isolated_mode, use_pep517=use_pep517, - config_settings=getattr(options, "config_settings", None), ) resolver_variant = cls.determine_resolver_variant(options) # The long import name and duplicated invocation is needed to convince diff --git a/src/pip/_internal/req/constructors.py b/src/pip/_internal/req/constructors.py index dc82a7e4f91..31c2421d761 100644 --- a/src/pip/_internal/req/constructors.py +++ b/src/pip/_internal/req/constructors.py @@ -416,7 +416,6 @@ def install_req_from_req_string( isolated: bool = False, use_pep517: Optional[bool] = None, user_supplied: bool = False, - config_settings: Optional[Dict[str, Union[str, List[str]]]] = None, ) -> InstallRequirement: try: req = get_requirement(req_string) @@ -446,7 +445,6 @@ def install_req_from_req_string( isolated=isolated, use_pep517=use_pep517, user_supplied=user_supplied, - config_settings=config_settings, ) diff --git a/tests/functional/test_config_settings.py b/tests/functional/test_config_settings.py index b1e15c01031..91643a3dc34 100644 --- a/tests/functional/test_config_settings.py +++ b/tests/functional/test_config_settings.py @@ -1,8 +1,10 @@ import json +import tarfile from pathlib import Path -from typing import Tuple +from typing import List, Optional, Tuple from zipfile import ZipFile +from pip._internal.utils.urls import path_to_url from tests.lib import PipTestEnvironment PYPROJECT_TOML = """\ @@ -36,9 +38,10 @@ Author: None Author-email: none@example.org License: MIT +{requires_dist} """ -def make_wheel(z, project, version, files): +def make_wheel(z, project, version, requires_dist, files): record = [] def add_file(name, data): data = data.encode("utf-8") @@ -48,7 +51,9 @@ def add_file(name, data): record.append((name, f"sha256={hash}", len(data))) distinfo = f"{project}-{version}.dist-info" add_file(f"{distinfo}/WHEEL", WHEEL) - add_file(f"{distinfo}/METADATA", METADATA.format(project=project, version=version)) + add_file(f"{distinfo}/METADATA", METADATA.format( + project=project, version=version, requires_dist=requires_dist + )) for name, data in files: add_file(name, data) record_name = f"{distinfo}/RECORD" @@ -70,14 +75,14 @@ def build_wheel( ): if config_settings is None: config_settings = {} - w = os.path.join(wheel_directory, "foo-1.0-py3-none-any.whl") + w = os.path.join(wheel_directory, "{{name}}-1.0-py3-none-any.whl") with open(w, "wb") as f: with ZipFile(f, "w") as z: make_wheel( - z, "foo", "1.0", - [("config.json", json.dumps(config_settings))] + z, "{{name}}", "1.0", "{{requires_dist}}", + [("{{name}}-config.json", json.dumps(config_settings))] ) - return "foo-1.0-py3-none-any.whl" + return "{{name}}-1.0-py3-none-any.whl" build_editable = build_wheel @@ -85,14 +90,20 @@ def build_wheel( ''' -def make_project(path: Path) -> Tuple[str, str, Path]: - name = "foo" +def make_project( + path: Path, name: str = "foo", dependencies: Optional[List[str]] = None +) -> Tuple[str, str, Path]: version = "1.0" project_dir = path / name backend = project_dir / "backend" backend.mkdir(parents=True) (project_dir / "pyproject.toml").write_text(PYPROJECT_TOML) - (backend / "dummy_backend.py").write_text(BACKEND_SRC) + requires_dist = [f"Requires-Dist: {dep}" for dep in dependencies or []] + (backend / "dummy_backend.py").write_text( + BACKEND_SRC.replace("{{name}}", name).replace( + "{{requires_dist}}", "\n".join(requires_dist) + ) + ) return name, version, project_dir @@ -108,25 +119,133 @@ def test_backend_sees_config(script: PipTestEnvironment) -> None: 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") + 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" + constraints_file.write_text(f"{name} @ {path_to_url(str(project_dir))}") + script.pip( + "wheel", + "--config-settings", + "FOO=Hello", + "-c", + "constraints.txt", + name, + ) + 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_sdist(script: PipTestEnvironment) -> None: + name, version, project_dir = make_project(script.scratch_path) + dists_dir = script.scratch_path / "dists" + dists_dir.mkdir() + with tarfile.open(dists_dir / f"{name}-{version}.tar.gz", "w:gz") as dist_tar: + dist_tar.add(project_dir, arcname=name) + script.pip( + "wheel", + "--config-settings", + "FOO=Hello", + "-f", + dists_dir, + name, + ) + 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_req_file_does_not_see_config(script: PipTestEnvironment) -> None: + """Test that CLI config settings do not propagate to requirement files.""" + name, _, project_dir = make_project(script.scratch_path) + reqs_file = script.scratch_path / "reqs.txt" + reqs_file.write_text(f"{project_dir}") + script.pip( + "install", + "--config-settings", + "FOO=Hello", + "-r", + reqs_file, + ) + config = script.site_packages_path / f"{name}-config.json" + with open(config, "rb") as f: + assert json.load(f) == {} + + +def test_dep_does_not_see_config(script: PipTestEnvironment) -> None: + """Test that CLI config settings do not propagate to dependencies.""" + _, _, bar_project_dir = make_project(script.scratch_path, name="bar") + _, _, foo_project_dir = make_project( + script.scratch_path, + name="foo", + dependencies=[f"bar @ {path_to_url(str(bar_project_dir))}"], + ) + script.pip( + "install", + "--config-settings", + "FOO=Hello", + foo_project_dir, + ) + foo_config = script.site_packages_path / "foo-config.json" + with open(foo_config, "rb") as f: + assert json.load(f) == {"FOO": "Hello"} + bar_config = script.site_packages_path / "bar-config.json" + with open(bar_config, "rb") as f: + assert json.load(f) == {} + + +def test_dep_in_req_file_does_not_see_config(script: PipTestEnvironment) -> None: + """Test that CLI config settings do not propagate to dependencies found in + requirement files.""" + _, _, bar_project_dir = make_project(script.scratch_path, name="bar") + _, _, foo_project_dir = make_project( + script.scratch_path, + name="foo", + dependencies=["bar"], + ) + reqs_file = script.scratch_path / "reqs.txt" + reqs_file.write_text(f"bar @ {path_to_url(str(bar_project_dir))}") + script.pip( + "install", + "--config-settings", + "FOO=Hello", + "-r", + reqs_file, + foo_project_dir, + ) + foo_config = script.site_packages_path / "foo-config.json" + with open(foo_config, "rb") as f: + assert json.load(f) == {"FOO": "Hello"} + bar_config = script.site_packages_path / "bar-config.json" + with open(bar_config, "rb") as f: + assert json.load(f) == {} + + def test_install_sees_config(script: PipTestEnvironment) -> None: - _, _, project_dir = make_project(script.scratch_path) + name, _, project_dir = make_project(script.scratch_path) script.pip( "install", "--config-settings", "FOO=Hello", project_dir, ) - config = script.site_packages_path / "config.json" + 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: - _, _, project_dir = make_project(script.scratch_path) + name, _, project_dir = make_project(script.scratch_path) script.pip( "install", "--config-settings", @@ -134,6 +253,6 @@ def test_install_editable_sees_config(script: PipTestEnvironment) -> None: "--editable", project_dir, ) - config = script.site_packages_path / "config.json" + config = script.site_packages_path / f"{name}-config.json" with open(config, "rb") as f: assert json.load(f) == {"FOO": "Hello"}