Skip to content

Commit

Permalink
Merge pull request #11059 from pfmoore/config_settings
Browse files Browse the repository at this point in the history
Add a UI to set config settings for PEP 517 backends
  • Loading branch information
pfmoore authored Apr 29, 2022
2 parents 6313d86 + 437bb67 commit cdeb8f9
Show file tree
Hide file tree
Showing 13 changed files with 336 additions and 1 deletion.
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 @@ -801,6 +801,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("=")
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),
)
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),
)
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),
)
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

0 comments on commit cdeb8f9

Please sign in to comment.