diff --git a/poetry.lock b/poetry.lock
index 3c0a674ebfa..a7c47a95490 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -390,15 +390,20 @@ testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "poetry-core"
-version = "1.1.0a7"
+version = "1.1.0-alpha.7"
description = "Poetry PEP 517 Build Backend"
category = "main"
optional = false
-python-versions = ">=3.7,<4.0"
+python-versions = "^3.7"
+develop = true
[package.dependencies]
importlib-metadata = {version = ">=1.7.0", markers = "python_version < \"3.8\""}
+[package.source]
+type = "directory"
+url = "../poetry-core"
+
[[package]]
name = "pre-commit"
version = "2.17.0"
@@ -707,7 +712,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-
[metadata]
lock-version = "1.1"
python-versions = "^3.7"
-content-hash = "e1979eb8cccef74af26d7fc01bb77d64515a2633fdb28bdbc2837da52fb24b98"
+content-hash = "dc08abc6f6c31200df3f9c163e59ff7b1fa32abb5ec1823b829d7bb760ad0cfc"
[metadata.files]
atomicwrites = [
@@ -984,10 +989,7 @@ pluggy = [
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
{file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
]
-poetry-core = [
- {file = "poetry-core-1.1.0a7.tar.gz", hash = "sha256:4622ae680842ac9b1b9c3b0e8dc467c2e291d1a5c434b6bd413907a2e5571d92"},
- {file = "poetry_core-1.1.0a7-py3-none-any.whl", hash = "sha256:724e8b5368f270461e622396305d0c2e760ec9d4c14d072e6b944da9384c67de"},
-]
+poetry-core = []
pre-commit = [
{file = "pre_commit-2.17.0-py2.py3-none-any.whl", hash = "sha256:725fa7459782d7bec5ead072810e47351de01709be838c2ce1726b9591dad616"},
{file = "pre_commit-2.17.0.tar.gz", hash = "sha256:c1a8040ff15ad3d648c70cc3e55b93e4d2d5b687320955505587fd79bbaed06a"},
diff --git a/pyproject.toml b/pyproject.toml
index ba13316c4f1..50ff7ab6b42 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -34,7 +34,7 @@ generate-setup-file = false
[tool.poetry.dependencies]
python = "^3.7"
-poetry-core = "^1.1.0a7"
+poetry-core = {path = "../poetry-core", develop = true}
cachecontrol = { version = "^0.12.9", extras = ["filecache"] }
cachy = "^0.3.0"
cleo = "^1.0.0a4"
diff --git a/src/poetry/console/commands/build.py b/src/poetry/console/commands/build.py
index 9d5545326ca..2ae31807df3 100644
--- a/src/poetry/console/commands/build.py
+++ b/src/poetry/console/commands/build.py
@@ -1,8 +1,15 @@
from __future__ import annotations
+from typing import TYPE_CHECKING
+
from cleo.helpers import option
from poetry.console.commands.env_command import EnvCommand
+from poetry.utils.env import build_environment
+
+
+if TYPE_CHECKING:
+ from poetry.utils.env import Env
class BuildCommand(EnvCommand):
@@ -23,11 +30,13 @@ class BuildCommand(EnvCommand):
def handle(self) -> None:
from poetry.core.masonry.builder import Builder
- fmt = self.option("format") or "all"
- package = self.poetry.package
- self.line(
- f"Building {package.pretty_name} ({package.version})"
- )
+ env: Env
+ with build_environment(poetry=self.poetry, env=self.env, io=self.io) as env:
+ fmt = self.option("format") or "all"
+ package = self.poetry.package
+ self.line(
+ f"Building {package.pretty_name} ({package.version})"
+ )
- builder = Builder(self.poetry)
- builder.build(fmt, executable=self.env.python)
+ builder = Builder(self.poetry)
+ builder.build(fmt, executable=env.python) # type: ignore[attr-defined]
diff --git a/src/poetry/masonry/builders/editable.py b/src/poetry/masonry/builders/editable.py
index 188ece7c47e..fe26e7ec64e 100644
--- a/src/poetry/masonry/builders/editable.py
+++ b/src/poetry/masonry/builders/editable.py
@@ -15,14 +15,15 @@
from poetry.utils._compat import WINDOWS
from poetry.utils._compat import decode
+from poetry.utils.env import build_environment
from poetry.utils.helpers import is_dir_writable
from poetry.utils.pip import pip_install
if TYPE_CHECKING:
from cleo.io.io import IO
- from poetry.core.poetry import Poetry
+ from poetry.poetry import Poetry
from poetry.utils.env import Env
SCRIPT_TEMPLATE = """\
@@ -75,8 +76,12 @@ def build(self) -> None:
self._add_dist_info(added_files)
def _run_build_script(self, build_script: Path) -> None:
- self._debug(f" - Executing build script: {build_script}")
- self._env.run("python", str(self._path.joinpath(build_script)), call=True)
+ env: Env
+ with build_environment(poetry=self._poetry, env=self._env, io=self._io) as env:
+ self._debug(f" - Executing build script: {build_script}")
+ env.run( # type: ignore[attr-defined]
+ "python", str(self._path.joinpath(build_script)), call=True
+ )
def _setup_build(self) -> None:
builder = SdistBuilder(self._poetry)
diff --git a/src/poetry/utils/env.py b/src/poetry/utils/env.py
index 544e5fe78ae..35265cf573f 100644
--- a/src/poetry/utils/env.py
+++ b/src/poetry/utils/env.py
@@ -1852,6 +1852,46 @@ def ephemeral_environment(
yield VirtualEnv(venv_dir, venv_dir)
+@contextmanager
+def build_environment(
+ poetry: Poetry, env: Env | None = None, io: IO | None = None
+) -> ContextManager[Env]:
+ """
+ If a build script is specified for the project, there could be additional build
+ time dependencies, eg: cython, setuptools etc. In these cases, we create an
+ ephemeral build environment with all requirements specified under
+ `build-system.requires` and return this. Otherwise, the given default project
+ environment is returned.
+ """
+ if not env or poetry.package.build_script:
+ with ephemeral_environment(executable=env.python if env else None) as venv:
+ overwrite = io and io.output.is_decorated() and not io.is_debug()
+ if io:
+ requires = map(
+ lambda r: f"{r}", poetry.pyproject.build_system.requires
+ )
+ if not overwrite:
+ io.write_line("")
+
+ io.overwrite(
+ "Preparing build environment with build-system requirements"
+ f" {', '.join(requires)}"
+ )
+ venv.run_pip(
+ "install",
+ "--disable-pip-version-check",
+ "--ignore-installed",
+ *poetry.pyproject.build_system.requires,
+ )
+
+ if overwrite:
+ io.write_line("")
+
+ yield venv
+ else:
+ yield env
+
+
class MockEnv(NullEnv):
def __init__(
self,
diff --git a/tests/fixtures/extended_project_without_setup/pyproject.toml b/tests/fixtures/extended_project_without_setup/pyproject.toml
index 5c9dc2774c7..b3f4818f29c 100644
--- a/tests/fixtures/extended_project_without_setup/pyproject.toml
+++ b/tests/fixtures/extended_project_without_setup/pyproject.toml
@@ -27,3 +27,7 @@ generate-setup-file = false
# Requirements
[tool.poetry.dependencies]
python = "~2.7 || ^3.4"
+
+[build-system]
+requires = ["poetry-core", "cython"]
+build-backend = "poetry.core.masonry.api"
diff --git a/tests/masonry/builders/test_editable_builder.py b/tests/masonry/builders/test_editable_builder.py
index cbd91f4dcdd..373d81d9f40 100644
--- a/tests/masonry/builders/test_editable_builder.py
+++ b/tests/masonry/builders/test_editable_builder.py
@@ -229,9 +229,13 @@ def test_builder_installs_proper_files_when_packages_configured(
def test_builder_should_execute_build_scripts(
- extended_without_setup_poetry: Poetry, tmp_dir: str
+ mocker: MockerFixture, extended_without_setup_poetry: Poetry, tmp_dir: str
):
env = MockEnv(path=Path(tmp_dir) / "foo")
+ mocker.patch(
+ "poetry.masonry.builders.editable.build_environment"
+ ).return_value.__enter__.return_value = env
+
builder = EditableBuilder(extended_without_setup_poetry, env, NullIO())
builder.build()
diff --git a/tests/utils/test_env.py b/tests/utils/test_env.py
index afc5aa9207a..c50822f1b63 100644
--- a/tests/utils/test_env.py
+++ b/tests/utils/test_env.py
@@ -25,9 +25,11 @@
from poetry.utils.env import EnvManager
from poetry.utils.env import GenericEnv
from poetry.utils.env import InvalidCurrentPythonVersionError
+from poetry.utils.env import MockEnv
from poetry.utils.env import NoCompatiblePythonVersionFound
from poetry.utils.env import SystemEnv
from poetry.utils.env import VirtualEnv
+from poetry.utils.env import build_environment
if TYPE_CHECKING:
@@ -1270,3 +1272,51 @@ def test_generate_env_name_ignores_case_for_case_insensitive_fs(tmp_dir: str):
assert venv_name1 == venv_name2
else:
assert venv_name1 != venv_name2
+
+
+@pytest.fixture()
+def extended_without_setup_poetry() -> Poetry:
+ poetry = Factory().create_poetry(
+ Path(__file__).parent.parent / "fixtures" / "extended_project_without_setup"
+ )
+
+ return poetry
+
+
+def test_build_environment_called_build_script_specified(
+ mocker: MockerFixture, extended_without_setup_poetry: Poetry, tmp_dir: str
+):
+ project_env = MockEnv(path=Path(tmp_dir) / "project")
+ ephemeral_env = MockEnv(path=Path(tmp_dir) / "ephemeral")
+
+ mocker.patch(
+ "poetry.utils.env.ephemeral_environment"
+ ).return_value.__enter__.return_value = ephemeral_env
+
+ with build_environment(extended_without_setup_poetry, project_env) as env:
+ assert env == ephemeral_env
+ assert env.executed == [
+ [
+ "python",
+ env.pip_embedded,
+ "install",
+ "--disable-pip-version-check",
+ "--ignore-installed",
+ *extended_without_setup_poetry.pyproject.build_system.requires,
+ ]
+ ]
+
+
+def test_build_environment_not_called_without_build_script_specified(
+ mocker: MockerFixture, poetry: Poetry, tmp_dir: str
+):
+ project_env = MockEnv(path=Path(tmp_dir) / "project")
+ ephemeral_env = MockEnv(path=Path(tmp_dir) / "ephemeral")
+
+ mocker.patch(
+ "poetry.utils.env.ephemeral_environment"
+ ).return_value.__enter__.return_value = ephemeral_env
+
+ with build_environment(poetry, project_env) as env:
+ assert env == project_env
+ assert not env.executed