Skip to content

Commit e964518

Browse files
committed
build.script: use build environment for execution
With this change, Poetry now creates an ephemeral build environment with all requirements specified under `build-system.requires` when a build script is specified. Otherwise, project environment is reused.
1 parent 720c358 commit e964518

File tree

8 files changed

+133
-19
lines changed

8 files changed

+133
-19
lines changed

poetry.lock

+9-7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ generate-setup-file = false
3434
[tool.poetry.dependencies]
3535
python = "^3.7"
3636

37-
poetry-core = "^1.1.0a7"
37+
poetry-core = {path = "../poetry-core", develop = true}
3838
cachecontrol = { version = "^0.12.9", extras = ["filecache"] }
3939
cachy = "^0.3.0"
4040
cleo = "^1.0.0a4"

src/poetry/console/commands/build.py

+16-7
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
from __future__ import annotations
22

3+
from typing import TYPE_CHECKING
4+
35
from cleo.helpers import option
46

57
from poetry.console.commands.env_command import EnvCommand
8+
from poetry.utils.env import build_environment
9+
10+
11+
if TYPE_CHECKING:
12+
from poetry.utils.env import Env
613

714

815
class BuildCommand(EnvCommand):
@@ -23,11 +30,13 @@ class BuildCommand(EnvCommand):
2330
def handle(self) -> None:
2431
from poetry.core.masonry.builder import Builder
2532

26-
fmt = self.option("format") or "all"
27-
package = self.poetry.package
28-
self.line(
29-
f"Building <c1>{package.pretty_name}</c1> (<c2>{package.version}</c2>)"
30-
)
33+
env: Env
34+
with build_environment(poetry=self.poetry, env=self.env, io=self.io) as env:
35+
fmt = self.option("format") or "all"
36+
package = self.poetry.package
37+
self.line(
38+
f"Building <c1>{package.pretty_name}</c1> (<c2>{package.version}</c2>)"
39+
)
3140

32-
builder = Builder(self.poetry)
33-
builder.build(fmt, executable=self.env.python)
41+
builder = Builder(self.poetry)
42+
builder.build(fmt, executable=env.python) # type: ignore[attr-defined]

src/poetry/masonry/builders/editable.py

+8-3
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,15 @@
1515

1616
from poetry.utils._compat import WINDOWS
1717
from poetry.utils._compat import decode
18+
from poetry.utils.env import build_environment
1819
from poetry.utils.helpers import is_dir_writable
1920
from poetry.utils.pip import pip_install
2021

2122

2223
if TYPE_CHECKING:
2324
from cleo.io.io import IO
24-
from poetry.core.poetry import Poetry
2525

26+
from poetry.poetry import Poetry
2627
from poetry.utils.env import Env
2728

2829
SCRIPT_TEMPLATE = """\
@@ -75,8 +76,12 @@ def build(self) -> None:
7576
self._add_dist_info(added_files)
7677

7778
def _run_build_script(self, build_script: Path) -> None:
78-
self._debug(f" - Executing build script: <b>{build_script}</b>")
79-
self._env.run("python", str(self._path.joinpath(build_script)), call=True)
79+
env: Env
80+
with build_environment(poetry=self._poetry, env=self._env, io=self._io) as env:
81+
self._debug(f" - Executing build script: <b>{build_script}</b>")
82+
env.run( # type: ignore[attr-defined]
83+
"python", str(self._path.joinpath(build_script)), call=True
84+
)
8085

8186
def _setup_build(self) -> None:
8287
builder = SdistBuilder(self._poetry)

src/poetry/utils/env.py

+40
Original file line numberDiff line numberDiff line change
@@ -1852,6 +1852,46 @@ def ephemeral_environment(
18521852
yield VirtualEnv(venv_dir, venv_dir)
18531853

18541854

1855+
@contextmanager
1856+
def build_environment(
1857+
poetry: Poetry, env: Env | None = None, io: IO | None = None
1858+
) -> ContextManager[Env]:
1859+
"""
1860+
If a build script is specified for the project, there could be additional build
1861+
time dependencies, eg: cython, setuptools etc. In these cases, we create an
1862+
ephemeral build environment with all requirements specified under
1863+
`build-system.requires` and return this. Otherwise, the given default project
1864+
environment is returned.
1865+
"""
1866+
if not env or poetry.package.build_script:
1867+
with ephemeral_environment(executable=env.python if env else None) as venv:
1868+
overwrite = io and io.output.is_decorated() and not io.is_debug()
1869+
if io:
1870+
requires = map(
1871+
lambda r: f"<c1>{r}</c1>", poetry.pyproject.build_system.requires
1872+
)
1873+
if not overwrite:
1874+
io.write_line("")
1875+
1876+
io.overwrite(
1877+
"<b>Preparing</b> build environment with build-system requirements"
1878+
f" {', '.join(requires)}"
1879+
)
1880+
venv.run_pip(
1881+
"install",
1882+
"--disable-pip-version-check",
1883+
"--ignore-installed",
1884+
*poetry.pyproject.build_system.requires,
1885+
)
1886+
1887+
if overwrite:
1888+
io.write_line("")
1889+
1890+
yield venv
1891+
else:
1892+
yield env
1893+
1894+
18551895
class MockEnv(NullEnv):
18561896
def __init__(
18571897
self,

tests/fixtures/extended_project_without_setup/pyproject.toml

+4
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,7 @@ generate-setup-file = false
2727
# Requirements
2828
[tool.poetry.dependencies]
2929
python = "~2.7 || ^3.4"
30+
31+
[build-system]
32+
requires = ["poetry-core", "cython"]
33+
build-backend = "poetry.core.masonry.api"

tests/masonry/builders/test_editable_builder.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -229,9 +229,13 @@ def test_builder_installs_proper_files_when_packages_configured(
229229

230230

231231
def test_builder_should_execute_build_scripts(
232-
extended_without_setup_poetry: Poetry, tmp_dir: str
232+
mocker: MockerFixture, extended_without_setup_poetry: Poetry, tmp_dir: str
233233
):
234234
env = MockEnv(path=Path(tmp_dir) / "foo")
235+
mocker.patch(
236+
"poetry.masonry.builders.editable.build_environment"
237+
).return_value.__enter__.return_value = env
238+
235239
builder = EditableBuilder(extended_without_setup_poetry, env, NullIO())
236240

237241
builder.build()

tests/utils/test_env.py

+50
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@
2525
from poetry.utils.env import EnvManager
2626
from poetry.utils.env import GenericEnv
2727
from poetry.utils.env import InvalidCurrentPythonVersionError
28+
from poetry.utils.env import MockEnv
2829
from poetry.utils.env import NoCompatiblePythonVersionFound
2930
from poetry.utils.env import SystemEnv
3031
from poetry.utils.env import VirtualEnv
32+
from poetry.utils.env import build_environment
3133

3234

3335
if TYPE_CHECKING:
@@ -1270,3 +1272,51 @@ def test_generate_env_name_ignores_case_for_case_insensitive_fs(tmp_dir: str):
12701272
assert venv_name1 == venv_name2
12711273
else:
12721274
assert venv_name1 != venv_name2
1275+
1276+
1277+
@pytest.fixture()
1278+
def extended_without_setup_poetry() -> Poetry:
1279+
poetry = Factory().create_poetry(
1280+
Path(__file__).parent.parent / "fixtures" / "extended_project_without_setup"
1281+
)
1282+
1283+
return poetry
1284+
1285+
1286+
def test_build_environment_called_build_script_specified(
1287+
mocker: MockerFixture, extended_without_setup_poetry: Poetry, tmp_dir: str
1288+
):
1289+
project_env = MockEnv(path=Path(tmp_dir) / "project")
1290+
ephemeral_env = MockEnv(path=Path(tmp_dir) / "ephemeral")
1291+
1292+
mocker.patch(
1293+
"poetry.utils.env.ephemeral_environment"
1294+
).return_value.__enter__.return_value = ephemeral_env
1295+
1296+
with build_environment(extended_without_setup_poetry, project_env) as env:
1297+
assert env == ephemeral_env
1298+
assert env.executed == [
1299+
[
1300+
"python",
1301+
env.pip_embedded,
1302+
"install",
1303+
"--disable-pip-version-check",
1304+
"--ignore-installed",
1305+
*extended_without_setup_poetry.pyproject.build_system.requires,
1306+
]
1307+
]
1308+
1309+
1310+
def test_build_environment_not_called_without_build_script_specified(
1311+
mocker: MockerFixture, poetry: Poetry, tmp_dir: str
1312+
):
1313+
project_env = MockEnv(path=Path(tmp_dir) / "project")
1314+
ephemeral_env = MockEnv(path=Path(tmp_dir) / "ephemeral")
1315+
1316+
mocker.patch(
1317+
"poetry.utils.env.ephemeral_environment"
1318+
).return_value.__enter__.return_value = ephemeral_env
1319+
1320+
with build_environment(poetry, project_env) as env:
1321+
assert env == project_env
1322+
assert not env.executed

0 commit comments

Comments
 (0)