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 a UI to set config settings for PEP 517 backends #11059

Merged
merged 10 commits into from
Apr 29, 2022
10 changes: 10 additions & 0 deletions docs/html/reference/build-system/pyproject-toml.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,16 @@ This is considered a stopgap solution until setuptools adds support for
regular {ref}`deprecation policy <Deprecation Policy>`.
```

### Backend Configuration

Build backends have the ability to accept configuration settings, which can
change the way the build is handled. These settings take the form of a
series of `key=value` pairs. The user can supply configuration settings
using the `--config-settings` command line option (which can be supplied
multiple times, in order to specify multiple settings).

The supplied configuration settings are passed to every backend hook call.

## Build output

It is the responsibility of the build backend to ensure that the output is
Expand Down
1 change: 1 addition & 0 deletions docs/html/reference/requirements-file-format.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ The options which can be applied to individual requirements are:

- {ref}`--install-option <install_--install-option>`
- {ref}`--global-option <install_--global-option>`
- {ref}`--config-settings <install_--config-settings>`
- `--hash` (for {ref}`Hash-checking mode`)

## Referring to other requirements files
Expand Down
1 change: 1 addition & 0 deletions news/11059.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add a user interface for supplying config settings to build backends.
27 changes: 27 additions & 0 deletions src/pip/_internal/cli/cmdoptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -794,6 +794,33 @@ def _handle_no_use_pep517(
help=SUPPRESS_HELP,
)


def _handle_config_settings(
option: Option, opt_str: str, value: str, parser: OptionParser
) -> None:
key, sep, val = value.partition("=")
pradyunsg marked this conversation as resolved.
Show resolved Hide resolved
if sep != "=":
parser.error(f"Arguments to {opt_str} must be of the form KEY=VAL") # noqa
dest = getattr(parser.values, option.dest)
if dest is None:
dest = {}
setattr(parser.values, option.dest, dest)
dest[key] = val


config_settings: Callable[..., Option] = partial(
Option,
"--config-settings",
dest="config_settings",
type=str,
action="callback",
callback=_handle_config_settings,
metavar="settings",
help="Configuration settings to be passed to the PEP 517 build backend. "
"Settings take the form KEY=VALUE. Use multiple --config-settings options "
"to pass multiple keys to the backend.",
)

install_options: Callable[..., Option] = partial(
Option,
"--install-option",
Expand Down
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 @@ -325,6 +325,7 @@ def make_resolver(
install_req_from_req_string,
isolated=options.isolated_mode,
use_pep517=use_pep517,
config_settings=getattr(options, "config_settings", None),
pfmoore marked this conversation as resolved.
Show resolved Hide resolved
)
suppress_build_failures = cls.determine_build_failure_suppression(options)
resolver_variant = cls.determine_resolver_variant(options)
Expand Down Expand Up @@ -397,6 +398,7 @@ def get_requirements(
isolated=options.isolated_mode,
use_pep517=options.use_pep517,
user_supplied=True,
config_settings=getattr(options, "config_settings", None),
pfmoore marked this conversation as resolved.
Show resolved Hide resolved
)
requirements.append(req_to_add)

Expand All @@ -406,6 +408,7 @@ def get_requirements(
user_supplied=True,
isolated=options.isolated_mode,
use_pep517=options.use_pep517,
config_settings=getattr(options, "config_settings", None),
pfmoore marked this conversation as resolved.
Show resolved Hide resolved
)
requirements.append(req_to_add)

Expand Down
1 change: 1 addition & 0 deletions src/pip/_internal/commands/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ def add_options(self) -> None:
self.cmd_opts.add_option(cmdoptions.use_pep517())
self.cmd_opts.add_option(cmdoptions.no_use_pep517())

self.cmd_opts.add_option(cmdoptions.config_settings())
self.cmd_opts.add_option(cmdoptions.install_options())
self.cmd_opts.add_option(cmdoptions.global_options())

Expand Down
1 change: 1 addition & 0 deletions src/pip/_internal/commands/wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ def add_options(self) -> None:
help="Don't verify if built wheel is valid.",
)

self.cmd_opts.add_option(cmdoptions.config_settings())
self.cmd_opts.add_option(cmdoptions.build_options())
self.cmd_opts.add_option(cmdoptions.global_options())

Expand Down
10 changes: 10 additions & 0 deletions src/pip/_internal/req/constructors.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ def install_req_from_editable(
constraint: bool = False,
user_supplied: bool = False,
permit_editable_wheels: bool = False,
config_settings: Optional[Dict[str, str]] = None,
) -> InstallRequirement:

parts = parse_req_from_editable(editable_req)
Expand All @@ -224,6 +225,7 @@ def install_req_from_editable(
install_options=options.get("install_options", []) if options else [],
global_options=options.get("global_options", []) if options else [],
hash_options=options.get("hashes", {}) if options else {},
config_settings=config_settings,
extras=parts.extras,
)

Expand Down Expand Up @@ -380,6 +382,7 @@ def install_req_from_line(
constraint: bool = False,
line_source: Optional[str] = None,
user_supplied: bool = False,
config_settings: Optional[Dict[str, str]] = None,
) -> InstallRequirement:
"""Creates an InstallRequirement from a name, which might be a
requirement, directory containing 'setup.py', filename, or URL.
Expand All @@ -399,6 +402,7 @@ def install_req_from_line(
install_options=options.get("install_options", []) if options else [],
global_options=options.get("global_options", []) if options else [],
hash_options=options.get("hashes", {}) if options else {},
config_settings=config_settings,
constraint=constraint,
extras=parts.extras,
user_supplied=user_supplied,
Expand All @@ -411,6 +415,7 @@ def install_req_from_req_string(
isolated: bool = False,
use_pep517: Optional[bool] = None,
user_supplied: bool = False,
config_settings: Optional[Dict[str, str]] = None,
) -> InstallRequirement:
try:
req = get_requirement(req_string)
Expand Down Expand Up @@ -440,6 +445,7 @@ def install_req_from_req_string(
isolated=isolated,
use_pep517=use_pep517,
user_supplied=user_supplied,
config_settings=config_settings,
)


Expand All @@ -448,6 +454,7 @@ def install_req_from_parsed_requirement(
isolated: bool = False,
use_pep517: Optional[bool] = None,
user_supplied: bool = False,
config_settings: Optional[Dict[str, str]] = None,
) -> InstallRequirement:
if parsed_req.is_editable:
req = install_req_from_editable(
Expand All @@ -457,6 +464,7 @@ def install_req_from_parsed_requirement(
constraint=parsed_req.constraint,
isolated=isolated,
user_supplied=user_supplied,
config_settings=config_settings,
)

else:
Expand All @@ -469,6 +477,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 All @@ -487,4 +496,5 @@ def install_req_from_link_and_ireq(
install_options=ireq.install_options,
global_options=ireq.global_options,
hash_options=ireq.hash_options,
config_settings=ireq.config_settings,
)
6 changes: 5 additions & 1 deletion src/pip/_internal/req/req_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
)
from pip._internal.utils.hashes import Hashes
from pip._internal.utils.misc import (
ConfiguredPep517HookCaller,
ask_path_exists,
backup_dir,
display_path,
Expand Down Expand Up @@ -80,6 +81,7 @@ def __init__(
install_options: Optional[List[str]] = None,
global_options: Optional[List[str]] = None,
hash_options: Optional[Dict[str, List[str]]] = None,
config_settings: Optional[Dict[str, str]] = None,
constraint: bool = False,
extras: Collection[str] = (),
user_supplied: bool = False,
Expand Down Expand Up @@ -138,6 +140,7 @@ def __init__(
self.install_options = install_options if install_options else []
self.global_options = global_options if global_options else []
self.hash_options = hash_options if hash_options else {}
self.config_settings = config_settings
# Set to True after successful preparation of this requirement
self.prepared = False
# User supplied requirement are explicitly requested for installation
Expand Down Expand Up @@ -469,7 +472,8 @@ def load_pyproject_toml(self) -> None:
requires, backend, check, backend_path = pyproject_toml_data
self.requirements_to_check = check
self.pyproject_requires = requires
self.pep517_backend = Pep517HookCaller(
self.pep517_backend = ConfiguredPep517HookCaller(
self,
self.unpacked_source_directory,
backend,
backend_path=backend_path,
Expand Down
3 changes: 3 additions & 0 deletions src/pip/_internal/resolution/resolvelib/candidates.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ def make_install_req_from_link(
global_options=template.global_options,
hashes=template.hash_options,
),
config_settings=template.config_settings,
)
ireq.original_link = template.original_link
ireq.link = link
Expand All @@ -92,6 +93,7 @@ def make_install_req_from_editable(
global_options=template.global_options,
hashes=template.hash_options,
),
config_settings=template.config_settings,
)


Expand All @@ -116,6 +118,7 @@ def _make_install_req_from_dist(
global_options=template.global_options,
hashes=template.hash_options,
),
config_settings=template.config_settings,
)
ireq.satisfied_by = dist
return ireq
Expand Down
91 changes: 91 additions & 0 deletions src/pip/_internal/utils/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
BinaryIO,
Callable,
ContextManager,
Dict,
Generator,
Iterable,
Iterator,
Expand All @@ -33,6 +34,7 @@
cast,
)

from pip._vendor.pep517 import Pep517HookCaller
from pip._vendor.tenacity import retry, stop_after_delay, wait_fixed

from pip import __version__
Expand All @@ -55,6 +57,7 @@
"captured_stdout",
"ensure_dir",
"remove_auth_from_url",
"ConfiguredPep517HookCaller",
]


Expand Down Expand Up @@ -630,3 +633,91 @@ def partition(
"""
t1, t2 = tee(iterable)
return filterfalse(pred, t1), filter(pred, t2)


class ConfiguredPep517HookCaller(Pep517HookCaller):
def __init__(
self,
config_holder: Any,
source_dir: str,
build_backend: str,
backend_path: Optional[str] = None,
runner: Optional[Callable[..., None]] = None,
python_executable: Optional[str] = None,
):
super().__init__(
source_dir, build_backend, backend_path, runner, python_executable
)
self.config_holder = config_holder

def build_wheel(
self,
wheel_directory: str,
config_settings: Optional[Dict[str, str]] = None,
metadata_directory: Optional[str] = None,
) -> str:
cs = self.config_holder.config_settings
return super().build_wheel(
wheel_directory, config_settings=cs, metadata_directory=metadata_directory
)

def build_sdist(
self, sdist_directory: str, config_settings: Optional[Dict[str, str]] = None
) -> str:
cs = self.config_holder.config_settings
return super().build_sdist(sdist_directory, config_settings=cs)

def build_editable(
self,
wheel_directory: str,
config_settings: Optional[Dict[str, str]] = None,
metadata_directory: Optional[str] = None,
) -> str:
cs = self.config_holder.config_settings
return super().build_editable(
wheel_directory, config_settings=cs, metadata_directory=metadata_directory
)

def get_requires_for_build_wheel(
self, config_settings: Optional[Dict[str, str]] = None
) -> List[str]:
cs = self.config_holder.config_settings
return super().get_requires_for_build_wheel(config_settings=cs)

def get_requires_for_build_sdist(
self, config_settings: Optional[Dict[str, str]] = None
) -> List[str]:
cs = self.config_holder.config_settings
return super().get_requires_for_build_sdist(config_settings=cs)

def get_requires_for_build_editable(
self, config_settings: Optional[Dict[str, str]] = None
) -> List[str]:
cs = self.config_holder.config_settings
return super().get_requires_for_build_editable(config_settings=cs)

def prepare_metadata_for_build_wheel(
self,
metadata_directory: str,
config_settings: Optional[Dict[str, str]] = None,
_allow_fallback: bool = True,
) -> str:
cs = self.config_holder.config_settings
return super().prepare_metadata_for_build_wheel(
metadata_directory=metadata_directory,
config_settings=cs,
_allow_fallback=_allow_fallback,
)

def prepare_metadata_for_build_editable(
self,
metadata_directory: str,
config_settings: Optional[Dict[str, str]] = None,
_allow_fallback: bool = True,
) -> str:
cs = self.config_holder.config_settings
return super().prepare_metadata_for_build_editable(
metadata_directory=metadata_directory,
config_settings=cs,
_allow_fallback=_allow_fallback,
)
Loading