Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix editable installations for Poetry packages #2400

Merged
merged 1 commit into from
May 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 24 additions & 27 deletions poetry/installation/pip_installer.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import os
import tempfile

from io import open
from subprocess import CalledProcessError

from clikit.api.io import IO
Expand Down Expand Up @@ -177,7 +176,6 @@ def create_temporary_requirement(self, package):
def install_directory(self, package):
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

Expand Down Expand Up @@ -208,40 +206,39 @@ def install_directory(self, package):
and pip_version >= pip_version_with_build_system_support
)

setup = os.path.join(req, "setup.py")
has_setup = os.path.exists(setup)
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()
if has_poetry:
package_poetry = Factory().create_poetry(pyproject.parent)
abn marked this conversation as resolved.
Show resolved Hide resolved
if package.develop and not package_poetry.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(package_poetry, 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
return
elif not has_build_system or package_poetry.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(package_poetry)

# 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))
with builder.setup_py():
if package.develop:
args.append("-e")

with open(setup, "w", encoding="utf-8") as f:
f.write(decode(builder.build_setup()))
args.append(req)

return self.run(*args)

if package.develop:
args.append("-e")

args.append(req)

try:
return self.run(*args)
finally:
if not has_setup and os.path.exists(setup):
os.remove(setup)
return self.run(*args)

def install_git(self, package):
from poetry.core.packages import Package
Expand Down
5 changes: 4 additions & 1 deletion poetry/masonry/builders/editable.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

SCRIPT_TEMPLATE = """\
#!{python}
from {module} import {callable_}
from {module} import {callable_holder}

if __name__ == '__main__':
{callable_}()
Expand Down Expand Up @@ -105,6 +105,8 @@ def _add_scripts(self):
for script in scripts:
name, script = script.split(" = ")
module, callable_ = script.split(":")
callable_holder = callable_.rsplit(".", 1)[0]

script_file = scripts_path.joinpath(name)
self._debug(
" - Adding the <c2>{}</c2> script to <b>{}</b>".format(
Expand All @@ -117,6 +119,7 @@ def _add_scripts(self):
SCRIPT_TEMPLATE.format(
python=self._env._bin("python"),
module=module,
callable_holder=callable_holder,
callable_=callable_,
)
)
Expand Down
13 changes: 13 additions & 0 deletions poetry/repositories/installed_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,19 @@ def load(cls, env): # type: (Env) -> InstalledRepository
is_standard_package = False

if is_standard_package:
if (
path.name.endswith(".dist-info")
and env.site_packages.joinpath(
"{}.pth".format(package.pretty_name)
).exists()
):
with env.site_packages.joinpath(
"{}.pth".format(package.pretty_name)
).open() as f:
directory = Path(f.readline().strip())
package.source_type = "directory"
package.source_url = directory.as_posix()

continue

src_path = env.path / "src"
Expand Down
1 change: 1 addition & 0 deletions tests/fixtures/simple_project/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ python = "~2.7 || ^3.4"

[tool.poetry.scripts]
foo = "foo:bar"
baz = "bar:baz.boom.bim"
27 changes: 26 additions & 1 deletion tests/masonry/builders/test_editable_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def test_builder_installs_proper_files_for_standard_packages(simple_poetry, tmp_

assert "poetry" == dist_info.joinpath("INSTALLER").read_text()
assert (
"[console_scripts]\nfoo=foo:bar\n\n"
"[console_scripts]\nbaz=bar:baz.boom.bim\nfoo=foo:bar\n\n"
== dist_info.joinpath("entry_points.txt").read_text()
)

Expand Down Expand Up @@ -109,11 +109,36 @@ def test_builder_installs_proper_files_for_standard_packages(simple_poetry, tmp_
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(tmp_venv._bin_dir.joinpath("baz")) 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

baz_script = """\
#!{python}
from bar import baz.boom

if __name__ == '__main__':
baz.boom.bim()
""".format(
python=tmp_venv._bin("python")
)

assert baz_script == tmp_venv._bin_dir.joinpath("baz").read_text()

foo_script = """\
#!{python}
from foo import bar

if __name__ == '__main__':
bar()
""".format(
python=tmp_venv._bin("python")
)

assert foo_script == tmp_venv._bin_dir.joinpath("foo").read_text()


def test_builder_falls_back_on_setup_and_pip_for_packages_with_build_scripts(
extended_poetry,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Metadata-Version: 2.1
Name: editable
Version: 2.3.4
Summary: Editable description.
License: MIT
Keywords: cli,commands
Author: Foo Bar
Author-email: [email protected]
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
Description-Content-Type: text/x-rst

Editable
####
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/path/to/editable
13 changes: 10 additions & 3 deletions tests/repositories/test_installed_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
zipp.Path(str(SITE_PACKAGES / "foo-0.1.0-py3.8.egg"), "EGG-INFO")
),
metadata.PathDistribution(VENDOR_DIR / "attrs-19.3.0.dist-info"),
metadata.PathDistribution(SITE_PACKAGES / "editable-2.3.4.dist-info"),
]


Expand Down Expand Up @@ -45,7 +46,7 @@ def test_load(mocker):
mocker.patch("poetry.repositories.installed_repository._VENDORS", str(VENDOR_DIR))
repository = InstalledRepository.load(MockEnv(path=ENV_DIR))

assert len(repository.packages) == 3
assert len(repository.packages) == 4

cleo = repository.packages[0]
assert cleo.name == "cleo"
Expand All @@ -55,11 +56,11 @@ def test_load(mocker):
== "Cleo allows you to create beautiful and testable command-line interfaces."
)

foo = repository.packages[1]
foo = repository.packages[2]
assert foo.name == "foo"
assert foo.version.text == "0.1.0"

pendulum = repository.packages[2]
pendulum = repository.packages[3]
assert pendulum.name == "pendulum"
assert pendulum.version.text == "2.0.5"
assert pendulum.description == "Python datetimes made easy"
Expand All @@ -69,3 +70,9 @@ def test_load(mocker):

for pkg in repository.packages:
assert pkg.name != "attrs"

editable = repository.packages[1]
assert "editable" == editable.name
assert "2.3.4" == editable.version.text
assert "directory" == editable.source_type
assert "/path/to/editable" == editable.source_url