-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Add build constraints #13534
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
base: main
Are you sure you want to change the base?
Add build constraints #13534
Changes from 4 commits
2e4086e
1aa1d32
db4fcf7
d9d5f5d
8d170b5
9f9032c
a9e81d7
4fbafeb
8943172
ef06010
d564457
ebd55e7
c41496e
e015f3d
b333b85
bc48f0b
fc1bfb5
74b08e1
41164aa
e53db93
f372c74
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
Add experimental build constraints support via ``--use-feature=build-constraint``. | ||
This allows constraining the versions of packages used during the build process | ||
(e.g., setuptools). Build constraints can be specified via ``PIP_BUILD_CONSTRAINT`` | ||
environment variable or ``--build-constraint`` flag. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,14 +11,15 @@ | |
from collections import OrderedDict | ||
from collections.abc import Iterable | ||
from types import TracebackType | ||
from typing import TYPE_CHECKING, Protocol | ||
from typing import TYPE_CHECKING, Protocol, TypedDict | ||
|
||
from pip._vendor.packaging.version import Version | ||
|
||
from pip import __file__ as pip_location | ||
from pip._internal.cli.spinners import open_spinner | ||
from pip._internal.locations import get_platlib, get_purelib, get_scheme | ||
from pip._internal.metadata import get_default_environment, get_environment | ||
from pip._internal.utils.deprecation import deprecated | ||
from pip._internal.utils.logging import VERBOSE | ||
from pip._internal.utils.packaging import get_requirement | ||
from pip._internal.utils.subprocess import call_subprocess | ||
|
@@ -31,6 +32,10 @@ | |
logger = logging.getLogger(__name__) | ||
|
||
|
||
class ExtraEnviron(TypedDict, total=False): | ||
extra_environ: dict[str, str] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the benefit of defining this typed dictionary? It seems superfluous IMO. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am all open to suggestions that don't make the code more awkward (e.g writing a much larger if block) and keep mypy happy. I could move it into the if type checking block though to eliminate any run time construction. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've moved in to type checking block, not sure what else to do. |
||
|
||
|
||
def _dedup(a: str, b: str) -> tuple[str] | tuple[str, str]: | ||
return (a, b) if a != b else (a,) | ||
|
||
|
@@ -101,8 +106,49 @@ class SubprocessBuildEnvironmentInstaller: | |
Install build dependencies by calling pip in a subprocess. | ||
""" | ||
|
||
def __init__(self, finder: PackageFinder) -> None: | ||
def __init__( | ||
self, | ||
finder: PackageFinder, | ||
build_constraints: list[str] | None = None, | ||
build_constraint_feature_enabled: bool = False, | ||
constraints: list[str] | None = None, | ||
) -> None: | ||
self.finder = finder | ||
self._build_constraints = build_constraints or [] | ||
self._build_constraint_feature_enabled = build_constraint_feature_enabled | ||
self._constraints = constraints or [] | ||
|
||
def _deprecation_constraint_check(self) -> None: | ||
""" | ||
Check for deprecation warning: PIP_CONSTRAINT affecting build environments. | ||
|
||
This warns when build-constraint feature is NOT enabled but regular constraints | ||
match what PIP_CONSTRAINT environment variable points to. | ||
""" | ||
if self._build_constraint_feature_enabled: | ||
return | ||
|
||
if not self._constraints: | ||
return | ||
|
||
if not os.environ.get("PIP_CONSTRAINT"): | ||
return | ||
|
||
pip_constraint_files = [ | ||
f.strip() for f in os.environ["PIP_CONSTRAINT"].split() if f.strip() | ||
] | ||
if pip_constraint_files and set(pip_constraint_files) == set(self._constraints): | ||
deprecated( | ||
reason=( | ||
"Setting PIP_CONSTRAINT will not affect " | ||
"build constraints in the future," | ||
), | ||
replacement=( | ||
'PIP_BUILD_CONSTRAINT with PIP_USE_FEATURE="build-constraint"' | ||
notatallshaw marked this conversation as resolved.
Show resolved
Hide resolved
|
||
), | ||
gone_in="26.2", | ||
issue=None, | ||
) | ||
|
||
def install( | ||
self, | ||
|
@@ -112,6 +158,8 @@ def install( | |
kind: str, | ||
for_req: InstallRequirement | None, | ||
) -> None: | ||
self._deprecation_constraint_check() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should probably issue this deprecation warning only once. In some scenarios, even a single build will trigger this deprecation twice as the build backend can request additional dependencies dynamically. ... although I say that having tried this locally... I can't get the deprecation to be printed twice. Hmm. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated, I'm not so sure this prevents it being called in the isolated build sub-process, but it will only be called once per process at least. |
||
|
||
finder = self.finder | ||
args: list[str] = [ | ||
sys.executable, | ||
|
@@ -167,13 +215,32 @@ def install( | |
args.append("--pre") | ||
if finder.prefer_binary: | ||
args.append("--prefer-binary") | ||
|
||
# Handle build constraints | ||
extra_environ: ExtraEnviron = {} | ||
if self._build_constraint_feature_enabled: | ||
# Build constraints must be passed as both constraints | ||
# and build constraints to the subprocess | ||
for constraint_file in self._build_constraints: | ||
args.extend(["--constraint", constraint_file]) | ||
args.extend(["--build-constraint", constraint_file]) | ||
args.extend(["--use-feature", "build-constraint"]) | ||
|
||
# If there are no build constraints but the build constraint | ||
# process is enabled then we must ignore regular constraints | ||
if not self._build_constraints: | ||
extra_environ = { | ||
"extra_environ": {"_PIP_IN_BUILD_IGNORE_CONSTRAINTS": "1"} | ||
} | ||
|
||
args.append("--") | ||
args.extend(requirements) | ||
with open_spinner(f"Installing {kind}") as spinner: | ||
call_subprocess( | ||
args, | ||
command_desc=f"pip subprocess to install {kind}", | ||
spinner=spinner, | ||
**extra_environ, | ||
) | ||
|
||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -101,6 +101,23 @@ def check_dist_restriction(options: Values, check_target: bool = False) -> None: | |
) | ||
|
||
|
||
def check_build_constraints(options: Values) -> None: | ||
"""Function for validating build constraint options. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good forward thinking here! |
||
|
||
:param options: The OptionParser options. | ||
""" | ||
if hasattr(options, "build_constraints") and options.build_constraints: | ||
if "build-constraint" not in options.features_enabled: | ||
raise CommandError( | ||
"To use --build-constraint, you must enable this feature with " | ||
"--use-feature=build-constraint." | ||
) | ||
if not options.build_isolation: | ||
raise CommandError( | ||
"--build-constraint cannot be used with --no-build-isolation." | ||
) | ||
|
||
|
||
def _path_option_check(option: Option, opt: str, value: str) -> str: | ||
return os.path.expanduser(value) | ||
|
||
|
@@ -430,6 +447,22 @@ def constraints() -> Option: | |
) | ||
|
||
|
||
def build_constraint() -> Option: | ||
return Option( | ||
"--build-constraint", | ||
dest="build_constraints", | ||
action="append", | ||
type="str", | ||
default=[], | ||
metavar="file", | ||
help=( | ||
"Constrain build dependencies using the given constraints file. " | ||
"This option can be used multiple times. " | ||
"Requires --use-feature=build-constraint." | ||
), | ||
) | ||
|
||
|
||
def requirements() -> Option: | ||
return Option( | ||
"-r", | ||
|
@@ -1072,6 +1105,7 @@ def check_list_path_option(options: Values) -> None: | |
default=[], | ||
choices=[ | ||
"fast-deps", | ||
"build-constraint", | ||
] | ||
+ ALWAYS_ENABLED_FEATURES, | ||
help="Enable new functionality, that may be backward incompatible.", | ||
|
Uh oh!
There was an error while loading. Please reload this page.