Commit abee30f0 by Sébastien Eustace Committed by GitHub

Fix editable installations (#2400)

parent e70ee311
import os import os
import tempfile import tempfile
from io import open
from subprocess import CalledProcessError from subprocess import CalledProcessError
from clikit.api.io import IO from clikit.api.io import IO
...@@ -177,7 +176,6 @@ class PipInstaller(BaseInstaller): ...@@ -177,7 +176,6 @@ class PipInstaller(BaseInstaller):
def install_directory(self, package): def install_directory(self, package):
from poetry.factory import Factory from poetry.factory import Factory
from poetry.io.null_io import NullIO from poetry.io.null_io import NullIO
from poetry.utils._compat import decode
from poetry.masonry.builders.editable import EditableBuilder from poetry.masonry.builders.editable import EditableBuilder
from poetry.utils.toml_file import TomlFile from poetry.utils.toml_file import TomlFile
...@@ -208,40 +206,39 @@ class PipInstaller(BaseInstaller): ...@@ -208,40 +206,39 @@ class PipInstaller(BaseInstaller):
and pip_version >= pip_version_with_build_system_support and pip_version >= pip_version_with_build_system_support
) )
setup = os.path.join(req, "setup.py") if has_poetry:
has_setup = os.path.exists(setup) package_poetry = Factory().create_poetry(pyproject.parent)
if has_poetry and package.develop and not package.build_script: if package.develop and not package_poetry.package.build_script:
# This is a Poetry package in editable mode # This is a Poetry package in editable mode
# we can use the EditableBuilder without going through pip # we can use the EditableBuilder without going through pip
# to install it, unless it has a build script. # to install it, unless it has a build script.
builder = EditableBuilder( builder = EditableBuilder(package_poetry, self._env, NullIO())
Factory().create_poetry(pyproject.parent), self._env, NullIO()
)
builder.build() builder.build()
return return
elif has_poetry and (not has_build_system or package.build_script): elif not has_build_system or package_poetry.package.build_script:
from poetry.core.masonry.builders.sdist import SdistBuilder from poetry.core.masonry.builders.sdist import SdistBuilder
# We need to rely on creating a temporary setup.py # We need to rely on creating a temporary setup.py
# file since the version of pip does not support # file since the version of pip does not support
# build-systems # build-systems
# We also need it for non-PEP-517 packages # We also need it for non-PEP-517 packages
builder = SdistBuilder(Factory().create_poetry(pyproject.parent)) builder = SdistBuilder(package_poetry)
with open(setup, "w", encoding="utf-8") as f: with builder.setup_py():
f.write(decode(builder.build_setup())) if package.develop:
args.append("-e")
args.append(req)
return self.run(*args)
if package.develop: if package.develop:
args.append("-e") args.append("-e")
args.append(req) args.append(req)
try:
return self.run(*args) return self.run(*args)
finally:
if not has_setup and os.path.exists(setup):
os.remove(setup)
def install_git(self, package): def install_git(self, package):
from poetry.core.packages import Package from poetry.core.packages import Package
......
...@@ -16,7 +16,7 @@ from poetry.utils._compat import decode ...@@ -16,7 +16,7 @@ from poetry.utils._compat import decode
SCRIPT_TEMPLATE = """\ SCRIPT_TEMPLATE = """\
#!{python} #!{python}
from {module} import {callable_} from {module} import {callable_holder}
if __name__ == '__main__': if __name__ == '__main__':
{callable_}() {callable_}()
...@@ -105,6 +105,8 @@ class EditableBuilder(Builder): ...@@ -105,6 +105,8 @@ class EditableBuilder(Builder):
for script in scripts: for script in scripts:
name, script = script.split(" = ") name, script = script.split(" = ")
module, callable_ = script.split(":") module, callable_ = script.split(":")
callable_holder = callable_.rsplit(".", 1)[0]
script_file = scripts_path.joinpath(name) script_file = scripts_path.joinpath(name)
self._debug( self._debug(
" - Adding the <c2>{}</c2> script to <b>{}</b>".format( " - Adding the <c2>{}</c2> script to <b>{}</b>".format(
...@@ -117,6 +119,7 @@ class EditableBuilder(Builder): ...@@ -117,6 +119,7 @@ class EditableBuilder(Builder):
SCRIPT_TEMPLATE.format( SCRIPT_TEMPLATE.format(
python=self._env._bin("python"), python=self._env._bin("python"),
module=module, module=module,
callable_holder=callable_holder,
callable_=callable_, callable_=callable_,
) )
) )
......
...@@ -49,6 +49,19 @@ class InstalledRepository(Repository): ...@@ -49,6 +49,19 @@ class InstalledRepository(Repository):
is_standard_package = False is_standard_package = False
if is_standard_package: 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 continue
src_path = env.path / "src" src_path = env.path / "src"
......
...@@ -26,3 +26,4 @@ python = "~2.7 || ^3.4" ...@@ -26,3 +26,4 @@ python = "~2.7 || ^3.4"
[tool.poetry.scripts] [tool.poetry.scripts]
foo = "foo:bar" foo = "foo:bar"
baz = "bar:baz.boom.bim"
...@@ -70,7 +70,7 @@ def test_builder_installs_proper_files_for_standard_packages(simple_poetry, tmp_ ...@@ -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 "poetry" == dist_info.joinpath("INSTALLER").read_text()
assert ( 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() == dist_info.joinpath("entry_points.txt").read_text()
) )
...@@ -109,11 +109,36 @@ My Package ...@@ -109,11 +109,36 @@ My Package
records = dist_info.joinpath("RECORD").read_text() records = dist_info.joinpath("RECORD").read_text()
assert str(tmp_venv.site_packages.joinpath("simple_project.pth")) in records 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("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("METADATA")) in records
assert str(dist_info.joinpath("INSTALLER")) 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("entry_points.txt")) in records
assert str(dist_info.joinpath("RECORD")) 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( def test_builder_falls_back_on_setup_and_pip_for_packages_with_build_scripts(
extended_poetry, extended_poetry,
......
Metadata-Version: 2.1
Name: editable
Version: 2.3.4
Summary: Editable description.
License: MIT
Keywords: cli,commands
Author: Foo Bar
Author-email: foo@bar.com
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
####
...@@ -17,6 +17,7 @@ INSTALLED_RESULTS = [ ...@@ -17,6 +17,7 @@ INSTALLED_RESULTS = [
zipp.Path(str(SITE_PACKAGES / "foo-0.1.0-py3.8.egg"), "EGG-INFO") 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(VENDOR_DIR / "attrs-19.3.0.dist-info"),
metadata.PathDistribution(SITE_PACKAGES / "editable-2.3.4.dist-info"),
] ]
...@@ -45,7 +46,7 @@ def test_load(mocker): ...@@ -45,7 +46,7 @@ def test_load(mocker):
mocker.patch("poetry.repositories.installed_repository._VENDORS", str(VENDOR_DIR)) mocker.patch("poetry.repositories.installed_repository._VENDORS", str(VENDOR_DIR))
repository = InstalledRepository.load(MockEnv(path=ENV_DIR)) repository = InstalledRepository.load(MockEnv(path=ENV_DIR))
assert len(repository.packages) == 3 assert len(repository.packages) == 4
cleo = repository.packages[0] cleo = repository.packages[0]
assert cleo.name == "cleo" assert cleo.name == "cleo"
...@@ -55,11 +56,11 @@ def test_load(mocker): ...@@ -55,11 +56,11 @@ def test_load(mocker):
== "Cleo allows you to create beautiful and testable command-line interfaces." == "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.name == "foo"
assert foo.version.text == "0.1.0" assert foo.version.text == "0.1.0"
pendulum = repository.packages[2] pendulum = repository.packages[3]
assert pendulum.name == "pendulum" assert pendulum.name == "pendulum"
assert pendulum.version.text == "2.0.5" assert pendulum.version.text == "2.0.5"
assert pendulum.description == "Python datetimes made easy" assert pendulum.description == "Python datetimes made easy"
...@@ -69,3 +70,9 @@ def test_load(mocker): ...@@ -69,3 +70,9 @@ def test_load(mocker):
for pkg in repository.packages: for pkg in repository.packages:
assert pkg.name != "attrs" 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
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