Commit a7a42905 by Arun Babu Neelicattu Committed by Bjorn Neergaard

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.
parent 42cfc561
...@@ -3,6 +3,7 @@ from __future__ import annotations ...@@ -3,6 +3,7 @@ from __future__ import annotations
from cleo.helpers import option from cleo.helpers import option
from poetry.console.commands.env_command import EnvCommand from poetry.console.commands.env_command import EnvCommand
from poetry.utils.env import build_environment
class BuildCommand(EnvCommand): class BuildCommand(EnvCommand):
...@@ -23,6 +24,7 @@ class BuildCommand(EnvCommand): ...@@ -23,6 +24,7 @@ class BuildCommand(EnvCommand):
def handle(self) -> None: def handle(self) -> None:
from poetry.core.masonry.builder import Builder from poetry.core.masonry.builder import Builder
with build_environment(poetry=self.poetry, env=self.env, io=self.io) as env:
fmt = self.option("format") or "all" fmt = self.option("format") or "all"
package = self.poetry.package package = self.poetry.package
self.line( self.line(
...@@ -30,4 +32,4 @@ class BuildCommand(EnvCommand): ...@@ -30,4 +32,4 @@ class BuildCommand(EnvCommand):
) )
builder = Builder(self.poetry) builder = Builder(self.poetry)
builder.build(fmt, executable=self.env.python) builder.build(fmt, executable=env.python)
...@@ -15,14 +15,15 @@ from poetry.core.semver.version import Version ...@@ -15,14 +15,15 @@ from poetry.core.semver.version import Version
from poetry.utils._compat import WINDOWS from poetry.utils._compat import WINDOWS
from poetry.utils._compat import decode from poetry.utils._compat import decode
from poetry.utils.env import build_environment
from poetry.utils.helpers import is_dir_writable from poetry.utils.helpers import is_dir_writable
from poetry.utils.pip import pip_install from poetry.utils.pip import pip_install
if TYPE_CHECKING: if TYPE_CHECKING:
from cleo.io.io import IO from cleo.io.io import IO
from poetry.core.poetry import Poetry
from poetry.poetry import Poetry
from poetry.utils.env import Env from poetry.utils.env import Env
SCRIPT_TEMPLATE = """\ SCRIPT_TEMPLATE = """\
...@@ -75,8 +76,9 @@ class EditableBuilder(Builder): # type: ignore[misc] ...@@ -75,8 +76,9 @@ class EditableBuilder(Builder): # type: ignore[misc]
self._add_dist_info(added_files) self._add_dist_info(added_files)
def _run_build_script(self, build_script: str) -> None: def _run_build_script(self, build_script: str) -> None:
with build_environment(poetry=self._poetry, env=self._env, io=self._io) as env:
self._debug(f" - Executing build script: <b>{build_script}</b>") self._debug(f" - Executing build script: <b>{build_script}</b>")
self._env.run("python", str(self._path.joinpath(build_script)), call=True) env.run("python", str(self._path.joinpath(build_script)), call=True)
def _setup_build(self) -> None: def _setup_build(self) -> None:
builder = SdistBuilder(self._poetry) builder = SdistBuilder(self._poetry)
......
...@@ -20,6 +20,7 @@ from typing import Any ...@@ -20,6 +20,7 @@ from typing import Any
from typing import ContextManager from typing import ContextManager
from typing import Iterable from typing import Iterable
from typing import Iterator from typing import Iterator
from typing import TypeVar
import packaging.tags import packaging.tags
import tomlkit import tomlkit
...@@ -30,6 +31,7 @@ from packaging.tags import Tag ...@@ -30,6 +31,7 @@ from packaging.tags import Tag
from packaging.tags import interpreter_name from packaging.tags import interpreter_name
from packaging.tags import interpreter_version from packaging.tags import interpreter_version
from packaging.tags import sys_tags from packaging.tags import sys_tags
from poetry.core.poetry import Poetry
from poetry.core.semver.helpers import parse_constraint from poetry.core.semver.helpers import parse_constraint
from poetry.core.semver.version import Version from poetry.core.semver.version import Version
from poetry.core.toml.file import TOMLFile from poetry.core.toml.file import TOMLFile
...@@ -50,7 +52,8 @@ if TYPE_CHECKING: ...@@ -50,7 +52,8 @@ if TYPE_CHECKING:
from cleo.io.io import IO from cleo.io.io import IO
from poetry.core.version.markers import BaseMarker from poetry.core.version.markers import BaseMarker
from poetry.poetry import Poetry
P = TypeVar("P", bound=Poetry)
GET_SYS_TAGS = f""" GET_SYS_TAGS = f"""
...@@ -494,7 +497,7 @@ class EnvManager: ...@@ -494,7 +497,7 @@ class EnvManager:
ENVS_FILE = "envs.toml" ENVS_FILE = "envs.toml"
def __init__(self, poetry: Poetry) -> None: def __init__(self, poetry: P) -> None:
self._poetry = poetry self._poetry = poetry
def _full_python_path(self, python: str) -> str: def _full_python_path(self, python: str) -> str:
...@@ -1839,6 +1842,48 @@ def ephemeral_environment( ...@@ -1839,6 +1842,48 @@ def ephemeral_environment(
yield VirtualEnv(venv_dir, venv_dir) yield VirtualEnv(venv_dir, venv_dir)
@contextmanager
def build_environment(
poetry: P, env: Env | None = None, io: IO | None = None
) -> Iterator[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:
if not overwrite:
io.write_line("")
requires = [
f"<c1>{requirement}</c1>"
for requirement in poetry.pyproject.build_system.requires
]
io.overwrite(
"<b>Preparing</b> 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): class MockEnv(NullEnv):
def __init__( def __init__(
self, self,
......
...@@ -27,3 +27,7 @@ generate-setup-file = false ...@@ -27,3 +27,7 @@ generate-setup-file = false
# Requirements # Requirements
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "~2.7 || ^3.4" python = "~2.7 || ^3.4"
[build-system]
requires = ["poetry-core", "cython"]
build-backend = "poetry.core.masonry.api"
...@@ -267,9 +267,13 @@ def test_builder_installs_proper_files_when_packages_configured( ...@@ -267,9 +267,13 @@ def test_builder_installs_proper_files_when_packages_configured(
def test_builder_should_execute_build_scripts( 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") 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 = EditableBuilder(extended_without_setup_poetry, env, NullIO())
builder.build() builder.build()
......
...@@ -25,9 +25,11 @@ from poetry.utils.env import EnvCommandError ...@@ -25,9 +25,11 @@ from poetry.utils.env import EnvCommandError
from poetry.utils.env import EnvManager from poetry.utils.env import EnvManager
from poetry.utils.env import GenericEnv from poetry.utils.env import GenericEnv
from poetry.utils.env import InvalidCurrentPythonVersionError from poetry.utils.env import InvalidCurrentPythonVersionError
from poetry.utils.env import MockEnv
from poetry.utils.env import NoCompatiblePythonVersionFound from poetry.utils.env import NoCompatiblePythonVersionFound
from poetry.utils.env import SystemEnv from poetry.utils.env import SystemEnv
from poetry.utils.env import VirtualEnv from poetry.utils.env import VirtualEnv
from poetry.utils.env import build_environment
from poetry.utils.helpers import remove_directory from poetry.utils.helpers import remove_directory
...@@ -1331,3 +1333,51 @@ def test_generate_env_name_ignores_case_for_case_insensitive_fs(tmp_dir: str): ...@@ -1331,3 +1333,51 @@ def test_generate_env_name_ignores_case_for_case_insensitive_fs(tmp_dir: str):
assert venv_name1 == venv_name2 assert venv_name1 == venv_name2
else: else:
assert venv_name1 != venv_name2 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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment