From 885907acd3c2b4bab8210f3b36b92238891be09b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Fri, 24 Apr 2020 18:10:26 +0200 Subject: [PATCH 1/3] Read sys_path in reverse to ensure we get the proper paths first --- poetry/repositories/installed_repository.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/poetry/repositories/installed_repository.py b/poetry/repositories/installed_repository.py index 74a6af5cce6..dacd30c17ba 100644 --- a/poetry/repositories/installed_repository.py +++ b/poetry/repositories/installed_repository.py @@ -14,13 +14,11 @@ class InstalledRepository(Repository): def load(cls, env): # type: (Env) -> InstalledRepository """ Load installed packages. - - For now, it uses the pip "freeze" command. """ repo = cls() seen = set() - for entry in env.sys_path: + for entry in reversed(env.sys_path): for distribution in sorted( metadata.distributions(path=[entry]), key=lambda d: str(d._path), ): From b3989dfb8628b834955f7a0cb7c5fa2e493969b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Fri, 24 Apr 2020 18:13:39 +0200 Subject: [PATCH 2/3] Add a paths property to environments --- poetry/utils/env.py | 63 +++++++++++++++++++++++++++++---------- tests/utils/test_env.py | 65 +++++++++-------------------------------- 2 files changed, 61 insertions(+), 67 deletions(-) diff --git a/poetry/utils/env.py b/poetry/utils/env.py index 2cfe507de6e..c392eb69822 100644 --- a/poetry/utils/env.py +++ b/poetry/utils/env.py @@ -101,6 +101,13 @@ print(json.dumps(sys.path)) """ +GET_PATHS = """\ +import json +import sysconfig + +print(json.dumps(sysconfig.get_paths())) +""" + CREATE_VENV_COMMAND = """\ path = {!r} @@ -734,6 +741,7 @@ def __init__(self, path, base=None): # type: (Path, Optional[Path]) -> None self._marker_env = None self._pip_version = None self._site_packages = None + self._paths = None @property def path(self): # type: () -> Path @@ -790,22 +798,7 @@ def pip_version(self): @property def site_packages(self): # type: () -> Path if self._site_packages is None: - site_packages = [] - dist_packages = [] - for entry in self.sys_path: - entry = Path(entry) - if entry.name == "site-packages": - site_packages.append(entry) - elif entry.name == "dist-packages": - dist_packages.append(entry) - - if not site_packages and not dist_packages: - raise RuntimeError("Unable to find the site-packages directory") - - if site_packages: - self._site_packages = site_packages[0] - else: - self._site_packages = dist_packages[0] + self._site_packages = Path(self.paths["purelib"]) return self._site_packages @@ -813,6 +806,13 @@ def site_packages(self): # type: () -> Path def sys_path(self): # type: () -> List[str] raise NotImplementedError() + @property + def paths(self): # type: () -> Dict[str, str] + if self._paths is None: + self._paths = self.get_paths() + + return self._paths + @classmethod def get_base_prefix(cls): # type: () -> Path if hasattr(sys, "real_prefix"): @@ -841,6 +841,9 @@ def config_var(self, var): # type: (str) -> Any def get_pip_version(self): # type: () -> Version raise NotImplementedError() + def get_paths(self): # type: () -> Dict[str, str] + raise NotImplementedError() + def is_valid_for_marker(self, marker): # type: (BaseMarker) -> bool return marker.validate(self.marker_env) @@ -961,6 +964,29 @@ def get_pip_command(self): # type: () -> List[str] # has a pip and use that return [sys.executable, "-m", "pip"] + def get_paths(self): # type: () -> Dict[str, str] + # We can't use sysconfig.get_paths() because + # on some distributions it does not return the proper paths + # (those used by pip for instance). We go through distutils + # to get the proper ones. + from distutils.core import Distribution + from distutils.command.install import SCHEME_KEYS # noqa + + d = Distribution() + d.parse_config_files() + obj = d.get_command_obj("install", create=True) + obj.finalize_options() + + paths = sysconfig.get_paths().copy() + for key in SCHEME_KEYS: + if key == "headers": + # headers is not a path returned by sysconfig.get_paths() + continue + + paths[key] = getattr(obj, "install_{}".format(key)) + + return paths + def get_marker_env(self): # type: () -> Dict[str, Any] if hasattr(sys, "implementation"): info = sys.implementation.version @@ -1071,6 +1097,11 @@ def get_pip_version(self): # type: () -> Version return Version.parse(m.group(1)) + def get_paths(self): # type: () -> Dict[str, str] + output = self.run("python", "-", input_=GET_PATHS) + + return json.loads(output) + def is_venv(self): # type: () -> bool return True diff --git a/tests/utils/test_env.py b/tests/utils/test_env.py index 98d8e96700e..cfd603e2780 100644 --- a/tests/utils/test_env.py +++ b/tests/utils/test_env.py @@ -9,11 +9,11 @@ from poetry.core.semver import Version from poetry.factory import Factory -from poetry.utils._compat import WINDOWS from poetry.utils._compat import Path from poetry.utils.env import EnvCommandError from poetry.utils.env import EnvManager from poetry.utils.env import NoCompatiblePythonVersionFound +from poetry.utils.env import SystemEnv from poetry.utils.env import VirtualEnv from poetry.utils.toml_file import TomlFile @@ -788,58 +788,21 @@ def test_activate_with_in_project_setting_does_not_fail_if_no_venvs_dir( assert not envs_file.exists() -def test_env_site_packages_should_find_the_site_packages_directory_if_standard(tmp_dir): - if WINDOWS: - site_packages = Path(tmp_dir).joinpath("Lib/site-packages") - else: - site_packages = Path(tmp_dir).joinpath( - "lib/python{}/site-packages".format( - ".".join(str(v) for v in sys.version_info[:2]) - ) - ) - - site_packages.mkdir(parents=True) - - env = MockVirtualEnv(Path(tmp_dir), Path(tmp_dir), sys_path=[str(site_packages)]) - - assert site_packages == env.site_packages - - -def test_env_site_packages_should_find_the_site_packages_directory_if_root(tmp_dir): - site_packages = Path(tmp_dir).joinpath("site-packages") - site_packages.mkdir(parents=True) - - env = MockVirtualEnv(Path(tmp_dir), Path(tmp_dir), sys_path=[str(site_packages)]) - - assert site_packages == env.site_packages +def test_system_env_has_correct_paths(): + env = SystemEnv(Path(sys.prefix)) + paths = env.paths -def test_env_site_packages_should_find_the_dist_packages_directory_if_necessary( - tmp_dir, -): - site_packages = Path(tmp_dir).joinpath("dist-packages") - site_packages.mkdir(parents=True) - - env = MockVirtualEnv(Path(tmp_dir), Path(tmp_dir), sys_path=[str(site_packages)]) - - assert site_packages == env.site_packages - - -def test_env_site_packages_should_prefer_site_packages_over_dist_packages(tmp_dir): - dist_packages = Path(tmp_dir).joinpath("dist-packages") - dist_packages.mkdir(parents=True) - site_packages = Path(tmp_dir).joinpath("site-packages") - site_packages.mkdir(parents=True) - - env = MockVirtualEnv( - Path(tmp_dir), Path(tmp_dir), sys_path=[str(dist_packages), str(site_packages)] - ) - - assert site_packages == env.site_packages + assert paths.get("purelib") is not None + assert paths.get("platlib") is not None + assert paths.get("scripts") is not None + assert env.site_packages == Path(paths["purelib"]) -def test_env_site_packages_should_raise_an_error_if_no_site_packages(tmp_dir): - env = MockVirtualEnv(Path(tmp_dir), Path(tmp_dir), sys_path=[]) +def test_venv_has_correct_paths(tmp_venv): + paths = tmp_venv.paths - with pytest.raises(RuntimeError): - env.site_packages + assert paths.get("purelib") is not None + assert paths.get("platlib") is not None + assert paths.get("scripts") is not None + assert tmp_venv.site_packages == Path(paths["purelib"]) From 3e0d519e5862c3cf35b0fa2d4a3d929a3f77cc84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Sun, 26 Apr 2020 20:36:08 +0200 Subject: [PATCH 3/3] Improve editable install for Poetry projects --- poetry/console/commands/install.py | 3 +- poetry/installation/pip_installer.py | 37 +++- poetry/masonry/builders/editable.py | 191 +++++++++++++----- poetry/utils/env.py | 3 + tests/fixtures/extended_project/README.rst | 2 + tests/fixtures/extended_project/build.py | 0 .../extended_project/__init__.py | 0 .../fixtures/extended_project/pyproject.toml | 30 +++ tests/fixtures/simple_project/pyproject.toml | 3 + .../masonry/builders/test_editable_builder.py | 136 +++++++++++++ 10 files changed, 341 insertions(+), 64 deletions(-) create mode 100644 tests/fixtures/extended_project/README.rst create mode 100644 tests/fixtures/extended_project/build.py create mode 100644 tests/fixtures/extended_project/extended_project/__init__.py create mode 100644 tests/fixtures/extended_project/pyproject.toml create mode 100644 tests/masonry/builders/test_editable_builder.py diff --git a/poetry/console/commands/install.py b/poetry/console/commands/install.py index 8a820c3d585..d567dac4783 100644 --- a/poetry/console/commands/install.py +++ b/poetry/console/commands/install.py @@ -39,7 +39,6 @@ class InstallCommand(EnvCommand): _loggers = ["poetry.repositories.pypi_repository"] def handle(self): - from clikit.io import NullIO from poetry.installation.installer import Installer from poetry.masonry.builders import EditableBuilder from poetry.core.masonry.utils.module import ModuleOrPackageNotFound @@ -69,7 +68,7 @@ def handle(self): return 0 try: - builder = EditableBuilder(self.poetry, self._env, NullIO()) + builder = EditableBuilder(self.poetry, self._env, self._io) except ModuleOrPackageNotFound: # This is likely due to the fact that the project is an application # not following the structure expected by Poetry diff --git a/poetry/installation/pip_installer.py b/poetry/installation/pip_installer.py index 44c7ecc37ad..1b6b47f8069 100644 --- a/poetry/installation/pip_installer.py +++ b/poetry/installation/pip_installer.py @@ -175,9 +175,10 @@ def create_temporary_requirement(self, package): return name def install_directory(self, package): - from poetry.core.masonry.builder import SdistBuilder from poetry.factory import Factory + from poetry.io.null_io import NullIO from poetry.utils._compat import decode + from poetry.masonry.builders.editable import EditableBuilder from poetry.utils.toml_file import TomlFile if package.root_dir: @@ -197,18 +198,36 @@ def install_directory(self, package): "tool" in pyproject_content and "poetry" in pyproject_content["tool"] ) # Even if there is a build system specified - # pip as of right now does not support it fully - # TODO: Check for pip version when proper PEP-517 support lands - # has_build_system = ("build-system" in pyproject_content) + # some versions of pip (< 19.0.0) don't understand it + # so we need to check the version of pip to know + # if we can rely on the build system + pip_version = self._env.pip_version + pip_version_with_build_system_support = pip_version.__class__(19, 0, 0) + has_build_system = ( + "build-system" in pyproject_content + and pip_version >= pip_version_with_build_system_support + ) setup = os.path.join(req, "setup.py") has_setup = os.path.exists(setup) - if not has_setup and has_poetry and (package.develop or not has_build_system): - # We actually need to rely on creating a temporary setup.py - # file since pip, as of this comment, does not support - # build-system for editable packages + if has_poetry and package.develop and not package.build_script: + # This is a Poetry package in editable mode + # we can use the EditableBuilder without going through pip + # to install it, unless it has a build script. + builder = EditableBuilder( + Factory().create_poetry(pyproject.parent), self._env, NullIO() + ) + builder.build() + + return + elif has_poetry and (not has_build_system or package.build_script): + from poetry.core.masonry.builders.sdist import SdistBuilder + + # We need to rely on creating a temporary setup.py + # file since the version of pip does not support + # build-systems # We also need it for non-PEP-517 packages - builder = SdistBuilder(Factory().create_poetry(pyproject.parent),) + builder = SdistBuilder(Factory().create_poetry(pyproject.parent)) with open(setup, "w", encoding="utf-8") as f: f.write(decode(builder.build_setup())) diff --git a/poetry/masonry/builders/editable.py b/poetry/masonry/builders/editable.py index e6924c574ff..c568814dbd6 100644 --- a/poetry/masonry/builders/editable.py +++ b/poetry/masonry/builders/editable.py @@ -1,16 +1,32 @@ from __future__ import unicode_literals +import hashlib import os import shutil -from collections import defaultdict +from base64 import urlsafe_b64encode from poetry.core.masonry.builders.builder import Builder from poetry.core.masonry.builders.sdist import SdistBuilder from poetry.core.semver.version import Version +from poetry.utils._compat import WINDOWS +from poetry.utils._compat import Path from poetry.utils._compat import decode +SCRIPT_TEMPLATE = """\ +#!{python} +from {module} import {callable_} + +if __name__ == '__main__': + {callable_}() +""" + +WINDOWS_CMD_TEMPLATE = """\ +@echo off\r\n"{python}" "%~dp0\\{script}" %*\r\n +""" + + class EditableBuilder(Builder): def __init__(self, poetry, env, io): super(EditableBuilder, self).__init__(poetry) @@ -19,7 +35,22 @@ def __init__(self, poetry, env, io): self._io = io def build(self): - return self._setup_build() + self._debug( + " - Building package {} in editable mode".format( + self._package.name + ) + ) + + if self._package.build_script: + self._debug( + " - Falling back on using a setup.py" + ) + return self._setup_build() + + added_files = [] + added_files += self._add_pth() + added_files += self._add_scripts() + self._add_dist_info(added_files) def _setup_build(self): builder = SdistBuilder(self._poetry) @@ -36,14 +67,14 @@ def _setup_build(self): try: if self._env.pip_version < Version(19, 0): - self._env.run_pip("install", "-e", str(self._path)) + self._env.run_pip("install", "-e", str(self._path), "--no-deps") else: # Temporarily rename pyproject.toml shutil.move( str(self._poetry.file), str(self._poetry.file.with_suffix(".tmp")) ) try: - self._env.run_pip("install", "-e", str(self._path)) + self._env.run_pip("install", "-e", str(self._path), "--no-deps") finally: shutil.move( str(self._poetry.file.with_suffix(".tmp")), @@ -53,71 +84,125 @@ def _setup_build(self): if not has_setup: os.remove(str(setup)) - def _build_egg_info(self): - egg_info = self._path / "{}.egg-info".format( - self._package.name.replace("-", "_") + def _add_pth(self): + pth = self._env.site_packages.joinpath(self._module.name).with_suffix(".pth") + self._debug( + " - Adding {} to {} for {}".format( + pth.name, self._env.site_packages, self._poetry.file.parent + ) ) - egg_info.mkdir(exist_ok=True) + with pth.open("w", encoding="utf-8") as f: + f.write(decode(str(self._poetry.file.parent.resolve()))) + + return [pth] + + def _add_scripts(self): + added = [] + entry_points = self.convert_entry_points() + scripts_path = Path(self._env.paths["scripts"]) + + scripts = entry_points.get("console_scripts", []) + for script in scripts: + name, script = script.split(" = ") + module, callable_ = script.split(":") + script_file = scripts_path.joinpath(name) + self._debug( + " - Adding the {} script to {}".format( + name, scripts_path + ) + ) + with script_file.open("w", encoding="utf-8") as f: + f.write( + decode( + SCRIPT_TEMPLATE.format( + python=self._env._bin("python"), + module=module, + callable_=callable_, + ) + ) + ) + + script_file.chmod(0o755) + + added.append(script_file) + + if WINDOWS: + cmd_script = script_file.with_suffix(".cmd") + cmd = WINDOWS_CMD_TEMPLATE.format( + python=self._env._bin("python"), script=name + ) + self._debug( + " - Adding the {} script wrapper to {}".format( + cmd_script.name, scripts_path + ) + ) + + with cmd_script.open("w", encoding="utf-8") as f: + f.write(decode(cmd)) + + added.append(cmd_script) - with egg_info.joinpath("PKG-INFO").open("w", encoding="utf-8") as f: - f.write(decode(self.get_metadata_content())) + return added - with egg_info.joinpath("entry_points.txt").open("w", encoding="utf-8") as f: - entry_points = self.convert_entry_points() + def _add_dist_info(self, added_files): + from poetry.core.masonry.builders.wheel import WheelBuilder - for group_name in sorted(entry_points): - f.write("[{}]\n".format(group_name)) - for ep in sorted(entry_points[group_name]): - f.write(ep.replace(" ", "") + "\n") + added_files = added_files[:] - f.write("\n") + builder = WheelBuilder(self._poetry) + dist_info = self._env.site_packages.joinpath(builder.dist_info) + + self._debug( + " - Adding the {} directory to {}".format( + dist_info.name, self._env.site_packages + ) + ) - with egg_info.joinpath("requires.txt").open("w", encoding="utf-8") as f: - f.write(self._generate_requires()) + if dist_info.exists(): + shutil.rmtree(str(dist_info)) - def _build_egg_link(self): - egg_link = self._env.site_packages / "{}.egg-link".format(self._package.name) - with egg_link.open("w", encoding="utf-8") as f: - f.write(str(self._poetry.file.parent.resolve()) + "\n") - f.write(".") + dist_info.mkdir() - def _add_easy_install_entry(self): - easy_install_pth = self._env.site_packages / "easy-install.pth" - path = str(self._poetry.file.parent.resolve()) - content = "" - if easy_install_pth.exists(): - with easy_install_pth.open(encoding="utf-8") as f: - content = f.read() + with dist_info.joinpath("METADATA").open("w", encoding="utf-8") as f: + builder._write_metadata_file(f) - if path in content: - return + added_files.append(dist_info.joinpath("METADATA")) - content += "{}\n".format(path) + with dist_info.joinpath("INSTALLER").open("w", encoding="utf-8") as f: + f.write("poetry") - with easy_install_pth.open("w", encoding="utf-8") as f: - f.write(content) + added_files.append(dist_info.joinpath("INSTALLER")) - def _generate_requires(self): - extras = defaultdict(list) + if self.convert_entry_points(): + with dist_info.joinpath("entry_points.txt").open( + "w", encoding="utf-8" + ) as f: + builder._write_entry_points(f) - requires = "" - for dep in sorted(self._package.requires, key=lambda d: d.name): - marker = dep.marker - if marker.is_any(): - requires += "{}\n".format(dep.base_pep_508_name) - continue + added_files.append(dist_info.joinpath("entry_points.txt")) - extras[str(marker)].append(dep.base_pep_508_name) + with dist_info.joinpath("RECORD").open("w", encoding="utf-8") as f: + for path in added_files: + hash = self._get_file_hash(path) + size = path.stat().st_size + f.write("{},sha256={},{}\n".format(str(path), hash, size)) - if extras: - requires += "\n" + # RECORD itself is recorded with no hash or size + f.write("{},,\n".format(dist_info.joinpath("RECORD"))) - for marker, deps in sorted(extras.items()): - requires += "[:{}]\n".format(marker) + def _get_file_hash(self, filepath): + hashsum = hashlib.sha256() + with filepath.open("rb") as src: + while True: + buf = src.read(1024 * 8) + if not buf: + break + hashsum.update(buf) - for dep in deps: - requires += dep + "\n" + src.seek(0) - requires += "\n" + return urlsafe_b64encode(hashsum.digest()).decode("ascii").rstrip("=") - return requires + def _debug(self, msg): + if self._io.is_debug(): + self._io.write_line(msg) diff --git a/poetry/utils/env.py b/poetry/utils/env.py index c392eb69822..013483f7167 100644 --- a/poetry/utils/env.py +++ b/poetry/utils/env.py @@ -1156,6 +1156,9 @@ def __init__(self, path=None, base=None, execute=False): self._execute = execute self.executed = [] + def get_pip_command(self): # type: () -> List[str] + return [self._bin("python"), "-m", "pip"] + def _run(self, cmd, **kwargs): self.executed.append(cmd) diff --git a/tests/fixtures/extended_project/README.rst b/tests/fixtures/extended_project/README.rst new file mode 100644 index 00000000000..f7fe15470f9 --- /dev/null +++ b/tests/fixtures/extended_project/README.rst @@ -0,0 +1,2 @@ +My Package +========== diff --git a/tests/fixtures/extended_project/build.py b/tests/fixtures/extended_project/build.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/fixtures/extended_project/extended_project/__init__.py b/tests/fixtures/extended_project/extended_project/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/fixtures/extended_project/pyproject.toml b/tests/fixtures/extended_project/pyproject.toml new file mode 100644 index 00000000000..954b12b3497 --- /dev/null +++ b/tests/fixtures/extended_project/pyproject.toml @@ -0,0 +1,30 @@ +[tool.poetry] +name = "extended-project" +version = "1.2.3" +description = "Some description." +authors = [ + "Sébastien Eustace " +] +license = "MIT" + +readme = "README.rst" + +homepage = "https://python-poetry.org" +repository = "https://github.com/python-poetry/poetry" +documentation = "https://python-poetry.org/docs" + +keywords = ["packaging", "dependency", "poetry"] + +classifiers = [ + "Topic :: Software Development :: Build Tools", + "Topic :: Software Development :: Libraries :: Python Modules" +] + +build = "build.py" + +# Requirements +[tool.poetry.dependencies] +python = "~2.7 || ^3.4" + +[tool.poetry.scripts] +foo = "foo:bar" diff --git a/tests/fixtures/simple_project/pyproject.toml b/tests/fixtures/simple_project/pyproject.toml index 72d0c0beeda..a20fddd7dac 100644 --- a/tests/fixtures/simple_project/pyproject.toml +++ b/tests/fixtures/simple_project/pyproject.toml @@ -23,3 +23,6 @@ classifiers = [ # Requirements [tool.poetry.dependencies] python = "~2.7 || ^3.4" + +[tool.poetry.scripts] +foo = "foo:bar" diff --git a/tests/masonry/builders/test_editable_builder.py b/tests/masonry/builders/test_editable_builder.py new file mode 100644 index 00000000000..8d8b4356521 --- /dev/null +++ b/tests/masonry/builders/test_editable_builder.py @@ -0,0 +1,136 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +import shutil + +import pytest + +from poetry.factory import Factory +from poetry.io.null_io import NullIO +from poetry.masonry.builders.editable import EditableBuilder +from poetry.utils._compat import Path +from poetry.utils.env import EnvManager +from poetry.utils.env import MockEnv +from poetry.utils.env import VirtualEnv + + +@pytest.fixture() +def simple_poetry(): + poetry = Factory().create_poetry( + Path(__file__).parent.parent.parent / "fixtures" / "simple_project" + ) + + return poetry + + +@pytest.fixture() +def extended_poetry(): + poetry = Factory().create_poetry( + Path(__file__).parent.parent.parent / "fixtures" / "extended_project" + ) + + return poetry + + +@pytest.fixture() +def env_manager(simple_poetry): + return EnvManager(simple_poetry) + + +@pytest.fixture +def tmp_venv(tmp_dir, env_manager): + venv_path = Path(tmp_dir) / "venv" + + env_manager.build_venv(str(venv_path)) + + venv = VirtualEnv(venv_path) + yield venv + + shutil.rmtree(str(venv.path)) + + +def test_builder_installs_proper_files_for_standard_packages(simple_poetry, tmp_venv): + builder = EditableBuilder(simple_poetry, tmp_venv, NullIO()) + + builder.build() + + assert tmp_venv._bin_dir.joinpath("foo").exists() + assert tmp_venv.site_packages.joinpath("simple_project.pth").exists() + assert ( + str(simple_poetry.file.parent.resolve()) + == tmp_venv.site_packages.joinpath("simple_project.pth").read_text() + ) + + dist_info = tmp_venv.site_packages.joinpath("simple_project-1.2.3.dist-info") + assert dist_info.exists() + assert dist_info.joinpath("INSTALLER").exists() + assert dist_info.joinpath("METADATA").exists() + assert dist_info.joinpath("RECORD").exists() + assert dist_info.joinpath("entry_points.txt").exists() + + assert "poetry" == dist_info.joinpath("INSTALLER").read_text() + assert ( + "[console_scripts]\nfoo=foo:bar\n\n" + == dist_info.joinpath("entry_points.txt").read_text() + ) + + metadata = """\ +Metadata-Version: 2.1 +Name: simple-project +Version: 1.2.3 +Summary: Some description. +Home-page: https://python-poetry.org +License: MIT +Keywords: packaging,dependency,poetry +Author: Sébastien Eustace +Author-email: sebastien@eustace.io +Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* +Classifier: License :: OSI Approved :: MIT License +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Topic :: Software Development :: Build Tools +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Project-URL: Documentation, https://python-poetry.org/docs +Project-URL: Repository, https://github.com/python-poetry/poetry +Description-Content-Type: text/x-rst + +My Package +========== + +""" + assert metadata == dist_info.joinpath("METADATA").read_text(encoding="utf-8") + + records = dist_info.joinpath("RECORD").read_text() + assert str(tmp_venv.site_packages.joinpath("simple_project.pth")) in records + assert str(tmp_venv._bin_dir.joinpath("foo")) in records + assert str(dist_info.joinpath("METADATA")) in records + assert str(dist_info.joinpath("INSTALLER")) in records + assert str(dist_info.joinpath("entry_points.txt")) in records + assert str(dist_info.joinpath("RECORD")) in records + + +def test_builder_falls_back_on_setup_and_pip_for_packages_with_build_scripts( + extended_poetry, +): + env = MockEnv(path=Path("/foo")) + builder = EditableBuilder(extended_poetry, env, NullIO()) + + builder.build() + + assert [ + [ + "python", + "-m", + "pip", + "install", + "-e", + str(extended_poetry.file.parent), + "--no-deps", + ] + ] == env.executed