Skip to content

Commit cdeb8f9

Browse files
authored
Merge pull request pypa#11059 from pfmoore/config_settings
Add a UI to set config settings for PEP 517 backends
2 parents 6313d86 + 437bb67 commit cdeb8f9

File tree

13 files changed

+336
-1
lines changed

13 files changed

+336
-1
lines changed

docs/html/reference/build-system/pyproject-toml.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,16 @@ This is considered a stopgap solution until setuptools adds support for
106106
regular {ref}`deprecation policy <Deprecation Policy>`.
107107
```
108108

109+
### Backend Configuration
110+
111+
Build backends have the ability to accept configuration settings, which can
112+
change the way the build is handled. These settings take the form of a
113+
series of `key=value` pairs. The user can supply configuration settings
114+
using the `--config-settings` command line option (which can be supplied
115+
multiple times, in order to specify multiple settings).
116+
117+
The supplied configuration settings are passed to every backend hook call.
118+
109119
## Build output
110120

111121
It is the responsibility of the build backend to ensure that the output is

docs/html/reference/requirements-file-format.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ The options which can be applied to individual requirements are:
111111

112112
- {ref}`--install-option <install_--install-option>`
113113
- {ref}`--global-option <install_--global-option>`
114+
- {ref}`--config-settings <install_--config-settings>`
114115
- `--hash` (for {ref}`Hash-checking mode`)
115116

116117
## Referring to other requirements files

news/11059.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add a user interface for supplying config settings to build backends.

src/pip/_internal/cli/cmdoptions.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -801,6 +801,33 @@ def _handle_no_use_pep517(
801801
help=SUPPRESS_HELP,
802802
)
803803

804+
805+
def _handle_config_settings(
806+
option: Option, opt_str: str, value: str, parser: OptionParser
807+
) -> None:
808+
key, sep, val = value.partition("=")
809+
if sep != "=":
810+
parser.error(f"Arguments to {opt_str} must be of the form KEY=VAL") # noqa
811+
dest = getattr(parser.values, option.dest)
812+
if dest is None:
813+
dest = {}
814+
setattr(parser.values, option.dest, dest)
815+
dest[key] = val
816+
817+
818+
config_settings: Callable[..., Option] = partial(
819+
Option,
820+
"--config-settings",
821+
dest="config_settings",
822+
type=str,
823+
action="callback",
824+
callback=_handle_config_settings,
825+
metavar="settings",
826+
help="Configuration settings to be passed to the PEP 517 build backend. "
827+
"Settings take the form KEY=VALUE. Use multiple --config-settings options "
828+
"to pass multiple keys to the backend.",
829+
)
830+
804831
install_options: Callable[..., Option] = partial(
805832
Option,
806833
"--install-option",

src/pip/_internal/cli/req_command.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,7 @@ def make_resolver(
325325
install_req_from_req_string,
326326
isolated=options.isolated_mode,
327327
use_pep517=use_pep517,
328+
config_settings=getattr(options, "config_settings", None),
328329
)
329330
suppress_build_failures = cls.determine_build_failure_suppression(options)
330331
resolver_variant = cls.determine_resolver_variant(options)
@@ -397,6 +398,7 @@ def get_requirements(
397398
isolated=options.isolated_mode,
398399
use_pep517=options.use_pep517,
399400
user_supplied=True,
401+
config_settings=getattr(options, "config_settings", None),
400402
)
401403
requirements.append(req_to_add)
402404

@@ -406,6 +408,7 @@ def get_requirements(
406408
user_supplied=True,
407409
isolated=options.isolated_mode,
408410
use_pep517=options.use_pep517,
411+
config_settings=getattr(options, "config_settings", None),
409412
)
410413
requirements.append(req_to_add)
411414

src/pip/_internal/commands/install.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ def add_options(self) -> None:
190190
self.cmd_opts.add_option(cmdoptions.use_pep517())
191191
self.cmd_opts.add_option(cmdoptions.no_use_pep517())
192192

193+
self.cmd_opts.add_option(cmdoptions.config_settings())
193194
self.cmd_opts.add_option(cmdoptions.install_options())
194195
self.cmd_opts.add_option(cmdoptions.global_options())
195196

src/pip/_internal/commands/wheel.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ def add_options(self) -> None:
7373
help="Don't verify if built wheel is valid.",
7474
)
7575

76+
self.cmd_opts.add_option(cmdoptions.config_settings())
7677
self.cmd_opts.add_option(cmdoptions.build_options())
7778
self.cmd_opts.add_option(cmdoptions.global_options())
7879

src/pip/_internal/req/constructors.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ def install_req_from_editable(
207207
constraint: bool = False,
208208
user_supplied: bool = False,
209209
permit_editable_wheels: bool = False,
210+
config_settings: Optional[Dict[str, str]] = None,
210211
) -> InstallRequirement:
211212

212213
parts = parse_req_from_editable(editable_req)
@@ -224,6 +225,7 @@ def install_req_from_editable(
224225
install_options=options.get("install_options", []) if options else [],
225226
global_options=options.get("global_options", []) if options else [],
226227
hash_options=options.get("hashes", {}) if options else {},
228+
config_settings=config_settings,
227229
extras=parts.extras,
228230
)
229231

@@ -380,6 +382,7 @@ def install_req_from_line(
380382
constraint: bool = False,
381383
line_source: Optional[str] = None,
382384
user_supplied: bool = False,
385+
config_settings: Optional[Dict[str, str]] = None,
383386
) -> InstallRequirement:
384387
"""Creates an InstallRequirement from a name, which might be a
385388
requirement, directory containing 'setup.py', filename, or URL.
@@ -399,6 +402,7 @@ def install_req_from_line(
399402
install_options=options.get("install_options", []) if options else [],
400403
global_options=options.get("global_options", []) if options else [],
401404
hash_options=options.get("hashes", {}) if options else {},
405+
config_settings=config_settings,
402406
constraint=constraint,
403407
extras=parts.extras,
404408
user_supplied=user_supplied,
@@ -411,6 +415,7 @@ def install_req_from_req_string(
411415
isolated: bool = False,
412416
use_pep517: Optional[bool] = None,
413417
user_supplied: bool = False,
418+
config_settings: Optional[Dict[str, str]] = None,
414419
) -> InstallRequirement:
415420
try:
416421
req = get_requirement(req_string)
@@ -440,6 +445,7 @@ def install_req_from_req_string(
440445
isolated=isolated,
441446
use_pep517=use_pep517,
442447
user_supplied=user_supplied,
448+
config_settings=config_settings,
443449
)
444450

445451

@@ -448,6 +454,7 @@ def install_req_from_parsed_requirement(
448454
isolated: bool = False,
449455
use_pep517: Optional[bool] = None,
450456
user_supplied: bool = False,
457+
config_settings: Optional[Dict[str, str]] = None,
451458
) -> InstallRequirement:
452459
if parsed_req.is_editable:
453460
req = install_req_from_editable(
@@ -457,6 +464,7 @@ def install_req_from_parsed_requirement(
457464
constraint=parsed_req.constraint,
458465
isolated=isolated,
459466
user_supplied=user_supplied,
467+
config_settings=config_settings,
460468
)
461469

462470
else:
@@ -469,6 +477,7 @@ def install_req_from_parsed_requirement(
469477
constraint=parsed_req.constraint,
470478
line_source=parsed_req.line_source,
471479
user_supplied=user_supplied,
480+
config_settings=config_settings,
472481
)
473482
return req
474483

@@ -487,4 +496,5 @@ def install_req_from_link_and_ireq(
487496
install_options=ireq.install_options,
488497
global_options=ireq.global_options,
489498
hash_options=ireq.hash_options,
499+
config_settings=ireq.config_settings,
490500
)

src/pip/_internal/req/req_install.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
)
4747
from pip._internal.utils.hashes import Hashes
4848
from pip._internal.utils.misc import (
49+
ConfiguredPep517HookCaller,
4950
ask_path_exists,
5051
backup_dir,
5152
display_path,
@@ -80,6 +81,7 @@ def __init__(
8081
install_options: Optional[List[str]] = None,
8182
global_options: Optional[List[str]] = None,
8283
hash_options: Optional[Dict[str, List[str]]] = None,
84+
config_settings: Optional[Dict[str, str]] = None,
8385
constraint: bool = False,
8486
extras: Collection[str] = (),
8587
user_supplied: bool = False,
@@ -138,6 +140,7 @@ def __init__(
138140
self.install_options = install_options if install_options else []
139141
self.global_options = global_options if global_options else []
140142
self.hash_options = hash_options if hash_options else {}
143+
self.config_settings = config_settings
141144
# Set to True after successful preparation of this requirement
142145
self.prepared = False
143146
# User supplied requirement are explicitly requested for installation
@@ -469,7 +472,8 @@ def load_pyproject_toml(self) -> None:
469472
requires, backend, check, backend_path = pyproject_toml_data
470473
self.requirements_to_check = check
471474
self.pyproject_requires = requires
472-
self.pep517_backend = Pep517HookCaller(
475+
self.pep517_backend = ConfiguredPep517HookCaller(
476+
self,
473477
self.unpacked_source_directory,
474478
backend,
475479
backend_path=backend_path,

src/pip/_internal/resolution/resolvelib/candidates.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ def make_install_req_from_link(
6969
global_options=template.global_options,
7070
hashes=template.hash_options,
7171
),
72+
config_settings=template.config_settings,
7273
)
7374
ireq.original_link = template.original_link
7475
ireq.link = link
@@ -92,6 +93,7 @@ def make_install_req_from_editable(
9293
global_options=template.global_options,
9394
hashes=template.hash_options,
9495
),
96+
config_settings=template.config_settings,
9597
)
9698

9799

@@ -116,6 +118,7 @@ def _make_install_req_from_dist(
116118
global_options=template.global_options,
117119
hashes=template.hash_options,
118120
),
121+
config_settings=template.config_settings,
119122
)
120123
ireq.satisfied_by = dist
121124
return ireq

src/pip/_internal/utils/misc.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
BinaryIO,
2222
Callable,
2323
ContextManager,
24+
Dict,
2425
Generator,
2526
Iterable,
2627
Iterator,
@@ -33,6 +34,7 @@
3334
cast,
3435
)
3536

37+
from pip._vendor.pep517 import Pep517HookCaller
3638
from pip._vendor.tenacity import retry, stop_after_delay, wait_fixed
3739

3840
from pip import __version__
@@ -55,6 +57,7 @@
5557
"captured_stdout",
5658
"ensure_dir",
5759
"remove_auth_from_url",
60+
"ConfiguredPep517HookCaller",
5861
]
5962

6063

@@ -630,3 +633,91 @@ def partition(
630633
"""
631634
t1, t2 = tee(iterable)
632635
return filterfalse(pred, t1), filter(pred, t2)
636+
637+
638+
class ConfiguredPep517HookCaller(Pep517HookCaller):
639+
def __init__(
640+
self,
641+
config_holder: Any,
642+
source_dir: str,
643+
build_backend: str,
644+
backend_path: Optional[str] = None,
645+
runner: Optional[Callable[..., None]] = None,
646+
python_executable: Optional[str] = None,
647+
):
648+
super().__init__(
649+
source_dir, build_backend, backend_path, runner, python_executable
650+
)
651+
self.config_holder = config_holder
652+
653+
def build_wheel(
654+
self,
655+
wheel_directory: str,
656+
config_settings: Optional[Dict[str, str]] = None,
657+
metadata_directory: Optional[str] = None,
658+
) -> str:
659+
cs = self.config_holder.config_settings
660+
return super().build_wheel(
661+
wheel_directory, config_settings=cs, metadata_directory=metadata_directory
662+
)
663+
664+
def build_sdist(
665+
self, sdist_directory: str, config_settings: Optional[Dict[str, str]] = None
666+
) -> str:
667+
cs = self.config_holder.config_settings
668+
return super().build_sdist(sdist_directory, config_settings=cs)
669+
670+
def build_editable(
671+
self,
672+
wheel_directory: str,
673+
config_settings: Optional[Dict[str, str]] = None,
674+
metadata_directory: Optional[str] = None,
675+
) -> str:
676+
cs = self.config_holder.config_settings
677+
return super().build_editable(
678+
wheel_directory, config_settings=cs, metadata_directory=metadata_directory
679+
)
680+
681+
def get_requires_for_build_wheel(
682+
self, config_settings: Optional[Dict[str, str]] = None
683+
) -> List[str]:
684+
cs = self.config_holder.config_settings
685+
return super().get_requires_for_build_wheel(config_settings=cs)
686+
687+
def get_requires_for_build_sdist(
688+
self, config_settings: Optional[Dict[str, str]] = None
689+
) -> List[str]:
690+
cs = self.config_holder.config_settings
691+
return super().get_requires_for_build_sdist(config_settings=cs)
692+
693+
def get_requires_for_build_editable(
694+
self, config_settings: Optional[Dict[str, str]] = None
695+
) -> List[str]:
696+
cs = self.config_holder.config_settings
697+
return super().get_requires_for_build_editable(config_settings=cs)
698+
699+
def prepare_metadata_for_build_wheel(
700+
self,
701+
metadata_directory: str,
702+
config_settings: Optional[Dict[str, str]] = None,
703+
_allow_fallback: bool = True,
704+
) -> str:
705+
cs = self.config_holder.config_settings
706+
return super().prepare_metadata_for_build_wheel(
707+
metadata_directory=metadata_directory,
708+
config_settings=cs,
709+
_allow_fallback=_allow_fallback,
710+
)
711+
712+
def prepare_metadata_for_build_editable(
713+
self,
714+
metadata_directory: str,
715+
config_settings: Optional[Dict[str, str]] = None,
716+
_allow_fallback: bool = True,
717+
) -> str:
718+
cs = self.config_holder.config_settings
719+
return super().prepare_metadata_for_build_editable(
720+
metadata_directory=metadata_directory,
721+
config_settings=cs,
722+
_allow_fallback=_allow_fallback,
723+
)

0 commit comments

Comments
 (0)