Repository: pypa/pip

Issue:
Per-requirement --config-settings not working for editable_mode
### Description

Context: I want to install a local package in editable mode in a way that is compatible with mypy.

If I install the package on its own via:

```
pip install -e /path/to/mypkg --config-settings editable_mode=strict
```

It works correctly.

However, if I add to my requirements.txt:

```
...
-e /path/to/mypkg --config-settings editable_mode=strict
...
```

and then `pip install -r requirements.txt`, it does not pick up the editable_mode setting and installs in the default way which is incompatible with mypy.

The package I am trying to install uses setup.py (no pyproject.toml).

### Expected behavior

As I understand it from https://github.com/pypa/pip/issues/11325 it should work to specify the --config-settings in my requirements.txt so I don't understand why it isn't working.

### pip version

23.3.2

### Python version

3.11.6

### OS

MacOS

### How to Reproduce

see description

### Output

_No response_

### Code of Conduct

- [X] I agree to follow the [PSF Code of Conduct](https://www.python.org/psf/conduct/).


Discussion:
Can you provide `mypkg` or some stripped down version of it to reproduce the problem please?

Resolving patch:
diff --git a/src/pip/_internal/req/req_file.py b/src/pip/_internal/req/req_file.py
--- a/src/pip/_internal/req/req_file.py
+++ b/src/pip/_internal/req/req_file.py
@@ -75,8 +75,16 @@
     cmdoptions.config_settings,
 ]
 
+SUPPORTED_OPTIONS_EDITABLE_REQ: List[Callable[..., optparse.Option]] = [
+    cmdoptions.config_settings,
+]
+
+
 # the 'dest' string values
 SUPPORTED_OPTIONS_REQ_DEST = [str(o().dest) for o in SUPPORTED_OPTIONS_REQ]
+SUPPORTED_OPTIONS_EDITABLE_REQ_DEST = [
+    str(o().dest) for o in SUPPORTED_OPTIONS_EDITABLE_REQ
+]
 
 logger = logging.getLogger(__name__)
 
@@ -178,31 +186,25 @@ def handle_requirement_line(
 
     assert line.is_requirement
 
+    # get the options that apply to requirements
     if line.is_editable:
-        # For editable requirements, we don't support per-requirement
-        # options, so just return the parsed requirement.
-        return ParsedRequirement(
-            requirement=line.requirement,
-            is_editable=line.is_editable,
-            comes_from=line_comes_from,
-            constraint=line.constraint,
-        )
+        supported_dest = SUPPORTED_OPTIONS_EDITABLE_REQ_DEST
     else:
-        # get the options that apply to requirements
-        req_options = {}
-        for dest in SUPPORTED_OPTIONS_REQ_DEST:
-            if dest in line.opts.__dict__ and line.opts.__dict__[dest]:
-                req_options[dest] = line.opts.__dict__[dest]
-
-        line_source = f"line {line.lineno} of {line.filename}"
-        return ParsedRequirement(
-            requirement=line.requirement,
-            is_editable=line.is_editable,
-            comes_from=line_comes_from,
-            constraint=line.constraint,
-            options=req_options,
-            line_source=line_source,
-        )
+        supported_dest = SUPPORTED_OPTIONS_REQ_DEST
+    req_options = {}
+    for dest in supported_dest:
+        if dest in line.opts.__dict__ and line.opts.__dict__[dest]:
+            req_options[dest] = line.opts.__dict__[dest]
+
+    line_source = f"line {line.lineno} of {line.filename}"
+    return ParsedRequirement(
+        requirement=line.requirement,
+        is_editable=line.is_editable,
+        comes_from=line_comes_from,
+        constraint=line.constraint,
+        options=req_options,
+        line_source=line_source,
+    )
 
 
 def handle_option_line(


Test patch:
diff --git a/tests/functional/test_pep660.py b/tests/functional/test_pep660.py
--- a/tests/functional/test_pep660.py
+++ b/tests/functional/test_pep660.py
@@ -37,7 +37,7 @@ def prepare_metadata_for_build_wheel(metadata_directory, config_settings=None):
 
 def build_wheel(wheel_directory, config_settings=None, metadata_directory=None):
     with open("log.txt", "a") as f:
-        print(":build_wheel called", file=f)
+        print(f":build_wheel called with config_settings={config_settings}", file=f)
     return _build_wheel(wheel_directory, config_settings, metadata_directory)
 """
 
@@ -55,7 +55,7 @@ def prepare_metadata_for_build_editable(metadata_directory, config_settings=None
 
 def build_editable(wheel_directory, config_settings=None, metadata_directory=None):
     with open("log.txt", "a") as f:
-        print(":build_editable called", file=f)
+        print(f":build_editable called with config_settings={config_settings}", file=f)
     return _build_wheel(wheel_directory, config_settings, metadata_directory)
 """
 # fmt: on
@@ -88,6 +88,16 @@ def _assert_hook_called(project_dir: Path, hook: str) -> None:
     assert f":{hook} called" in log, f"{hook} has not been called"
 
 
+def _assert_hook_called_with_config_settings(
+    project_dir: Path, hook: str, config_settings: Dict[str, str]
+) -> None:
+    log = project_dir.joinpath("log.txt").read_text()
+    assert f":{hook} called" in log, f"{hook} has not been called"
+    assert (
+        f":{hook} called with config_settings={config_settings}" in log
+    ), f"{hook} has not been called with the expected config settings:\n{log}"
+
+
 def _assert_hook_not_called(project_dir: Path, hook: str) -> None:
     log = project_dir.joinpath("log.txt").read_text()
     assert f":{hook} called" not in log, f"{hook} should not have been called"
@@ -119,9 +129,35 @@ def test_install_pep660_basic(tmpdir: Path, script: PipTestEnvironment) -> None:
         "--no-build-isolation",
         "--editable",
         project_dir,
+        "--config-setting",
+        "x=y",
+    )
+    _assert_hook_called(project_dir, "prepare_metadata_for_build_editable")
+    _assert_hook_called_with_config_settings(project_dir, "build_editable", {"x": "y"})
+    assert (
+        result.test_env.site_packages.joinpath("project.egg-link")
+        not in result.files_created
+    ), "a .egg-link file should not have been created"
+
+
+def test_install_pep660_from_reqs_file(
+    tmpdir: Path, script: PipTestEnvironment
+) -> None:
+    """
+    Test with backend that supports build_editable.
+    """
+    project_dir = _make_project(tmpdir, BACKEND_WITH_PEP660, with_setup_py=False)
+    reqs_file = tmpdir / "requirements.txt"
+    reqs_file.write_text(f"-e {project_dir.as_uri()} --config-setting x=y\n")
+    result = script.pip(
+        "install",
+        "--no-index",
+        "--no-build-isolation",
+        "-r",
+        reqs_file,
     )
     _assert_hook_called(project_dir, "prepare_metadata_for_build_editable")
-    _assert_hook_called(project_dir, "build_editable")
+    _assert_hook_called_with_config_settings(project_dir, "build_editable", {"x": "y"})
     assert (
         result.test_env.site_packages.joinpath("project.egg-link")
         not in result.files_created