Skip to content

Commit 6fdcf23

Browse files
authored
Improve handling of PEP 518 build requirements
Merge pull request #5286 from benoit-pierre/improve_pep518_build_requirements_handling
2 parents 21b97e4 + 92e6e19 commit 6fdcf23

32 files changed

+161
-126
lines changed

docs/reference/pip.rst

Lines changed: 21 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -116,23 +116,10 @@ using an incorrect encoding (mojibake).
116116
PEP 518 Support
117117
~~~~~~~~~~~~~~~
118118

119-
Pip supports projects declaring dependencies that are required at install time
120-
using a ``pyproject.toml`` file, in the form described in `PEP518`_. When
121-
building a project, pip will install the required dependencies locally, and
122-
make them available to the build process.
123-
124-
As noted in the PEP, the minimum requirements for pip to be able to build a
125-
project are::
126-
127-
[build-system]
128-
# Minimum requirements for the build system to execute.
129-
requires = ["setuptools", "wheel"]
130-
131-
``setuptools`` and ``wheel`` **must** be included in any ``pyproject.toml``
132-
provided by a project - pip will assume these as a default, but will not add
133-
them to an explicitly supplied list in a project supplied ``pyproject.toml``
134-
file. Once `PEP517`_ support is added, this restriction will be lifted and
135-
alternative build tools will be allowed.
119+
As of 10.0, pip supports projects declaring dependencies that are required at
120+
install time using a ``pyproject.toml`` file, in the form described in
121+
`PEP518`_. When building a project, pip will install the required dependencies
122+
locally, and make them available to the build process.
136123

137124
When making build requirements available, pip does so in an *isolated
138125
environment*. That is, pip does not install those requirements into the user's
@@ -152,17 +139,23 @@ appropriately.
152139

153140
.. _pep-518-limitations:
154141

155-
The current implementation of `PEP518`_ in pip requires that any dependencies
156-
specified in ``pyproject.toml`` are available as wheels. This is a technical
157-
limitation of the implementation - dependencies only available as source would
158-
require a build step of their own, which would recursively invoke the `PEP518`_
159-
dependency installation process. The potentially unbounded recursion involved
160-
was not considered acceptable, and so installation of build dependencies from
161-
source has been disabled until a safe resolution of this issue has been found.
162-
163-
Further, it also doesn't support the use of environment markers and extras,
164-
only version specifiers are respected. Support for markers and extras will be
165-
added in a future release.
142+
**Limitations**:
143+
144+
* until `PEP517`_ support is added, ``setuptools`` and ``wheel`` **must** be
145+
included in the list of build requirements: pip will assume these as default,
146+
but will not automatically add them to the list of build requirements if
147+
explicitly defined in ``pyproject.toml``.
148+
149+
* the current implementation only support installing build requirements from
150+
wheels: this is a technical limitation of the implementation - source
151+
installs would require a build step of their own, potentially recursively
152+
triggering another `PEP518`_ dependency installation process. The possible
153+
unbounded recursion involved was not considered acceptable, and so
154+
installation of build dependencies from source has been disabled until a safe
155+
resolution of this issue is found.
156+
157+
* ``pip<18.0`` does not support the use of environment markers and extras, only
158+
version specifiers are respected.
166159

167160
.. _PEP517: http://www.python.org/dev/peps/pep-0517/
168161
.. _PEP518: http://www.python.org/dev/peps/pep-0518/

news/5230.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Improve handling of PEP 518 build requirements: support environment markers and extras.

news/5265.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Improve handling of PEP 518 build requirements: support environment markers and extras.

src/pip/_internal/build_env.py

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,33 @@
11
"""Build Environment used for isolation during sdist building
22
"""
33

4+
import logging
45
import os
6+
import sys
57
from distutils.sysconfig import get_python_lib
68
from sysconfig import get_paths
79

10+
from pip._internal.utils.misc import call_subprocess
811
from pip._internal.utils.temp_dir import TempDirectory
12+
from pip._internal.utils.ui import open_spinner
13+
14+
15+
logger = logging.getLogger(__name__)
916

1017

1118
class BuildEnvironment(object):
1219
"""Creates and manages an isolated environment to install build deps
1320
"""
1421

15-
def __init__(self, no_clean):
22+
def __init__(self):
1623
self._temp_dir = TempDirectory(kind="build-env")
17-
self._no_clean = no_clean
24+
self._temp_dir.create()
1825

1926
@property
2027
def path(self):
2128
return self._temp_dir.path
2229

2330
def __enter__(self):
24-
self._temp_dir.create()
25-
2631
self.save_path = os.environ.get('PATH', None)
2732
self.save_pythonpath = os.environ.get('PYTHONPATH', None)
2833
self.save_nousersite = os.environ.get('PYTHONNOUSERSITE', None)
@@ -58,9 +63,6 @@ def __enter__(self):
5863
return self.path
5964

6065
def __exit__(self, exc_type, exc_val, exc_tb):
61-
if not self._no_clean:
62-
self._temp_dir.cleanup()
63-
6466
def restore_var(varname, old_value):
6567
if old_value is None:
6668
os.environ.pop(varname, None)
@@ -74,12 +76,39 @@ def restore_var(varname, old_value):
7476
def cleanup(self):
7577
self._temp_dir.cleanup()
7678

79+
def install_requirements(self, finder, requirements, message):
80+
args = [
81+
sys.executable, '-m', 'pip', 'install', '--ignore-installed',
82+
'--no-user', '--prefix', self.path, '--no-warn-script-location',
83+
'--only-binary', ':all:',
84+
]
85+
if logger.getEffectiveLevel() <= logging.DEBUG:
86+
args.append('-v')
87+
if finder.index_urls:
88+
args.extend(['-i', finder.index_urls[0]])
89+
for extra_index in finder.index_urls[1:]:
90+
args.extend(['--extra-index-url', extra_index])
91+
else:
92+
args.append('--no-index')
93+
for link in finder.find_links:
94+
args.extend(['--find-links', link])
95+
for _, host, _ in finder.secure_origins:
96+
args.extend(['--trusted-host', host])
97+
if finder.allow_all_prereleases:
98+
args.append('--pre')
99+
if finder.process_dependency_links:
100+
args.append('--process-dependency-links')
101+
args.append('--')
102+
args.extend(requirements)
103+
with open_spinner(message) as spinner:
104+
call_subprocess(args, show_stdout=False, spinner=spinner)
105+
77106

78107
class NoOpBuildEnvironment(BuildEnvironment):
79108
"""A no-op drop-in replacement for BuildEnvironment
80109
"""
81110

82-
def __init__(self, no_clean):
111+
def __init__(self):
83112
pass
84113

85114
def __enter__(self):
@@ -90,3 +119,6 @@ def __exit__(self, exc_type, exc_val, exc_tb):
90119

91120
def cleanup(self):
92121
pass
122+
123+
def install_requirements(self, finder, requirements, message):
124+
raise NotImplementedError()

src/pip/_internal/operations/prepare.py

Lines changed: 6 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
"""Prepares a distribution for installation
22
"""
33

4-
import itertools
54
import logging
65
import os
7-
import sys
8-
from copy import copy
96

107
from pip._vendor import pkg_resources, requests
118

12-
from pip._internal.build_env import NoOpBuildEnvironment
9+
from pip._internal.build_env import BuildEnvironment
1310
from pip._internal.compat import expanduser
1411
from pip._internal.download import (
1512
is_dir_url, is_file_url, is_vcs_url, unpack_url, url_to_path,
@@ -18,14 +15,9 @@
1815
DirectoryUrlHashUnsupported, HashUnpinned, InstallationError,
1916
PreviousBuildDirError, VcsHashUnsupported,
2017
)
21-
from pip._internal.index import FormatControl
22-
from pip._internal.req.req_install import InstallRequirement
2318
from pip._internal.utils.hashes import MissingHashes
2419
from pip._internal.utils.logging import indent_log
25-
from pip._internal.utils.misc import (
26-
call_subprocess, display_path, normalize_path,
27-
)
28-
from pip._internal.utils.ui import open_spinner
20+
from pip._internal.utils.misc import display_path, normalize_path
2921
from pip._internal.vcs import vcs
3022

3123
logger = logging.getLogger(__name__)
@@ -47,26 +39,6 @@ def make_abstract_dist(req):
4739
return IsSDist(req)
4840

4941

50-
def _install_build_reqs(finder, prefix, build_requirements):
51-
# NOTE: What follows is not a very good thing.
52-
# Eventually, this should move into the BuildEnvironment class and
53-
# that should handle all the isolation and sub-process invocation.
54-
finder = copy(finder)
55-
finder.format_control = FormatControl(set(), set([":all:"]))
56-
urls = [
57-
finder.find_requirement(
58-
InstallRequirement.from_line(r), upgrade=False).url
59-
for r in build_requirements
60-
]
61-
args = [
62-
sys.executable, '-m', 'pip', 'install', '--ignore-installed',
63-
'--no-user', '--prefix', prefix,
64-
] + list(urls)
65-
66-
with open_spinner("Installing build dependencies") as spinner:
67-
call_subprocess(args, show_stdout=False, spinner=spinner)
68-
69-
7042
class DistAbstraction(object):
7143
"""Abstracts out the wheel vs non-wheel Resolver.resolve() logic.
7244
@@ -144,12 +116,10 @@ def format_reqs(rs):
144116
)
145117

146118
if should_isolate:
147-
with self.req.build_env:
148-
pass
149-
_install_build_reqs(finder, self.req.build_env.path,
150-
build_requirements)
151-
else:
152-
self.req.build_env = NoOpBuildEnvironment(no_clean=False)
119+
self.req.build_env = BuildEnvironment()
120+
self.req.build_env.install_requirements(
121+
finder, build_requirements,
122+
"Installing build dependencies")
153123

154124
self.req.run_egg_info()
155125
self.req.assert_source_matches_version()

src/pip/_internal/req/req_install.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from pip._vendor.pkg_resources import RequirementParseError, parse_requirements
2323

2424
from pip._internal import wheel
25-
from pip._internal.build_env import BuildEnvironment
25+
from pip._internal.build_env import NoOpBuildEnvironment
2626
from pip._internal.compat import native_str
2727
from pip._internal.download import (
2828
is_archive_file, is_url, path_to_url, url_to_path,
@@ -127,7 +127,7 @@ def __init__(self, req, comes_from, source_dir=None, editable=False,
127127
self.is_direct = False
128128

129129
self.isolated = isolated
130-
self.build_env = BuildEnvironment(no_clean=True)
130+
self.build_env = NoOpBuildEnvironment()
131131

132132
@classmethod
133133
def from_editable(cls, editable_req, comes_from=None, isolated=False,

src/pip/_internal/wheel.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
from pip._vendor.six import StringIO
2525

2626
from pip._internal import pep425tags
27-
from pip._internal.build_env import BuildEnvironment
2827
from pip._internal.download import path_to_url, unpack_url
2928
from pip._internal.exceptions import (
3029
InstallationError, InvalidWheelFilename, UnsupportedWheel,

tests/data/packages/pep518-3.0.tar.gz

-48 Bytes
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

tests/data/src/pep518-3.0/setup.cfg

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +0,0 @@
1-
[egg_info]
2-
tag_build =
3-
tag_date = 0
4-
tag_svn_revision = 0
5-

tests/data/src/pep518-3.0/setup.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
#!/usr/bin/env python
2-
from setuptools import find_packages, setup
2+
from setuptools import setup
33

4-
import simple # ensure dependency is installed
4+
import simplewheel # ensure dependency is installed
55

66
setup(name='pep518',
77
version='3.0',
8-
packages=find_packages()
8+
py_modules=['pep518'],
99
)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
include pyproject.toml
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
#dummy
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[build-system]
2+
requires=[
3+
"requires_simple_extra[extra]",
4+
"simplewheel==1.0; python_version < '3'",
5+
"simplewheel==2.0; python_version >= '3'",
6+
"setuptools", "wheel",
7+
]

tests/data/src/pep518_with_extra_and_markers-1.0/setup.cfg

Whitespace-only changes.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/usr/bin/env python
2+
import sys
3+
4+
from setuptools import setup
5+
6+
# ensure dependencies are installed
7+
import simple
8+
import simplewheel
9+
10+
assert simplewheel.__version__ == '1.0' if sys.version_info < (3,) else '2.0'
11+
12+
setup(name='pep518_with_extra_and_markers',
13+
version='1.0',
14+
py_modules=['pep518_with_extra_and_markers'],
15+
)

tests/data/src/simplewheel-1.0/setup.cfg

Whitespace-only changes.
Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
#!/usr/bin/env python
2-
from setuptools import find_packages, setup
2+
from setuptools import setup
3+
4+
import simplewheel
35

46
setup(name='simplewheel',
5-
version='1.0',
6-
packages=find_packages()
7+
version=simplewheel.__version__,
8+
packages=['simplewheel'],
79
)

tests/data/src/simplewheel-1.0/simple/__init__.py

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__version__ = '1.0'

tests/data/src/simplewheel-2.0/setup.cfg

Whitespace-only changes.
Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
#!/usr/bin/env python
2-
from setuptools import find_packages, setup
2+
from setuptools import setup
3+
4+
import simplewheel
35

46
setup(name='simplewheel',
5-
version='2.0',
6-
packages=find_packages()
7+
version=simplewheel.__version__,
8+
packages=['simplewheel'],
79
)

tests/data/src/simplewheel-2.0/simple/__init__.py

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__version__ = '2.0'

0 commit comments

Comments
 (0)