Skip to content

Commit

Permalink
Ensure the self update command is compatible with the new installer
Browse files Browse the repository at this point in the history
  • Loading branch information
sdispater committed Jun 18, 2021
1 parent 9246cf3 commit 8fae35f
Show file tree
Hide file tree
Showing 3 changed files with 221 additions and 7 deletions.
147 changes: 144 additions & 3 deletions poetry/console/commands/self/update.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import os
import re
import shutil
import site
import stat
import subprocess
import sys
Expand All @@ -16,6 +17,8 @@
from cleo import option

from poetry.core.packages import Dependency
from poetry.utils._compat import PY2
from poetry.utils._compat import Path

from ..command import Command

Expand Down Expand Up @@ -60,6 +63,10 @@ class SelfUpdateCommand(Command):
REPOSITORY_URL = "https://github.com/python-poetry/poetry"
BASE_URL = REPOSITORY_URL + "/releases/download"

_data_dir = None
_bin_dir = None
_pool = None

@property
def home(self):
from poetry.utils._compat import Path
Expand All @@ -78,18 +85,75 @@ def lib(self):
def lib_backup(self):
return self.home / "lib-backup"

@property
def data_dir(self): # type: () -> Path
if self._data_dir is not None:
return self._data_dir

from poetry.locations import data_dir

self._data_dir = data_dir()

return self._data_dir

@property
def bin_dir(self): # type: () -> Path
if self._data_dir is not None:
return self._data_dir

from poetry.utils._compat import WINDOWS

if os.getenv("POETRY_HOME"):
return Path(os.getenv("POETRY_HOME"), "bin").expanduser()

user_base = site.getuserbase()

if WINDOWS:
bin_dir = os.path.join(user_base, "Scripts")
else:
bin_dir = os.path.join(user_base, "bin")

self._bin_dir = Path(bin_dir)

return self._bin_dir

@property
def pool(self):
if self._pool is not None:
return self._pool

from poetry.repositories.pool import Pool
from poetry.repositories.pypi_repository import PyPiRepository

pool = Pool()
pool.add_repository(PyPiRepository(fallback=False))

self._pool = pool

return self._pool

def handle(self):
from poetry.__version__ import __version__
from poetry.core.semver import Version
from poetry.repositories.pypi_repository import PyPiRepository
from poetry.utils.env import EnvManager

new_update_method = False
try:
self._check_recommended_installation()
except RuntimeError as e:
env = EnvManager.get_system_env(naive=True)
try:
env.path.relative_to(self.data_dir)
except ValueError:
raise e

self._check_recommended_installation()
new_update_method = True

version = self.argument("version")
if not version:
version = ">=" + __version__

repo = PyPiRepository(fallback=False)
repo = self.pool.repositories[0]
packages = repo.find_packages(
Dependency("poetry", version, allows_prereleases=self.option("preview"))
)
Expand Down Expand Up @@ -127,6 +191,9 @@ def handle(self):
self.line("You are using the latest version")
return

if new_update_method:
return self.update_with_new_method(release.version)

self.update(release)

def update(self, release):
Expand Down Expand Up @@ -165,6 +232,18 @@ def update(self, release):
)
)

def update_with_new_method(self, version):
self.line("Updating <c1>Poetry</c1> to <c2>{}</c2>".format(version))
self.line("")

self._update_with_new_method(version)
self._make_bin()

self.line("")
self.line(
"<c1>Poetry</c1> (<c2>{}</c2>) is installed now. Great!".format(version)
)

def _update(self, version):
from poetry.utils.helpers import temporary_directory

Expand Down Expand Up @@ -235,6 +314,68 @@ def _update(self, version):
finally:
gz.close()

def _update_with_new_method(self, version):
from poetry.config.config import Config
from poetry.core.packages.dependency import Dependency
from poetry.core.packages.project_package import ProjectPackage
from poetry.installation.installer import Installer
from poetry.packages.locker import NullLocker
from poetry.repositories.installed_repository import InstalledRepository
from poetry.utils.env import EnvManager

env = EnvManager.get_system_env()
installed = InstalledRepository.load(env)

root = ProjectPackage("poetry-updater", "0.0.0")
root.python_versions = ".".join(str(c) for c in env.version_info[:3])
root.add_dependency(Dependency("poetry", version.text))

installer = Installer(
self.io,
env,
root,
NullLocker(self.data_dir.joinpath("poetry.lock"), {}),
self.pool,
Config(),
installed=installed,
)
installer.update(True)
installer.run()

def _make_bin(self):
from poetry.utils._compat import WINDOWS

self.line("")
self.line("Updating the <c1>poetry</c1> script")

self.bin_dir.mkdir(parents=True, exist_ok=True)

script = "poetry"
target_script = "venv/bin/poetry"
if WINDOWS:
script = "poetry.exe"
target_script = "venv/Scripts/poetry.exe"

if self.bin_dir.joinpath(script).exists():
self.bin_dir.joinpath(script).unlink()

if not PY2 and not WINDOWS:
try:
self.bin_dir.joinpath(script).symlink_to(
self.data_dir.joinpath(target_script)
)
except OSError:
# This can happen if the user
# does not have the correct permission on Windows
shutil.copy(
self.data_dir.joinpath(target_script), self.bin_dir.joinpath(script)
)
else:
shutil.copy(
str(self.data_dir.joinpath(target_script)),
str(self.bin_dir.joinpath(script)),
)

def process(self, *args):
return subprocess.check_output(list(args), stderr=subprocess.STDOUT)

Expand Down
5 changes: 5 additions & 0 deletions poetry/packages/locker.py
Original file line number Diff line number Diff line change
Expand Up @@ -586,3 +586,8 @@ def _dump_package(self, package): # type: (Package) -> dict
data["develop"] = package.develop

return data


class NullLocker(Locker):
def set_lock_data(self, root, packages): # type: (Package, List[Package]) -> None
pass
76 changes: 72 additions & 4 deletions tests/console/commands/self/test_update.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@
from poetry.__version__ import __version__
from poetry.core.packages.package import Package
from poetry.core.semver.version import Version
from poetry.factory import Factory
from poetry.repositories.installed_repository import InstalledRepository
from poetry.repositories.pool import Pool
from poetry.repositories.repository import Repository
from poetry.utils._compat import WINDOWS
from poetry.utils._compat import Path
from poetry.utils.env import EnvManager


FIXTURES = Path(__file__).parent.joinpath("fixtures")
Expand All @@ -25,10 +30,13 @@ def test_self_update_should_install_all_necessary_elements(
command = tester._command

version = Version.parse(__version__).next_minor.text
mocker.patch(
"poetry.repositories.pypi_repository.PyPiRepository.find_packages",
return_value=[Package("poetry", version)],
)
repository = Repository()
repository.add_package(Package("poetry", version))

pool = Pool()
pool.add_repository(repository)

command._pool = pool
mocker.patch.object(command, "_check_recommended_installation", return_value=None)
mocker.patch.object(
command, "_get_release_name", return_value="poetry-{}-darwin".format(version)
Expand Down Expand Up @@ -89,3 +97,63 @@ def test_self_update_should_install_all_necessary_elements(

assert lib.exists()
assert lib.joinpath("poetry").exists()


def test_self_update_can_update_from_recommended_installation(
tester, http, mocker, environ, tmp_venv
):
mocker.patch.object(EnvManager, "get_system_env", return_value=tmp_venv)
target_script = tmp_venv.path.parent.joinpath("venv/bin/poetry")
if WINDOWS:
target_script = tmp_venv.path.parent.joinpath("venv/bin/poetry")

target_script.parent.mkdir(parents=True, exist_ok=True)
target_script.touch()

command = tester._command
command._data_dir = tmp_venv.path.parent

new_version = Version.parse(__version__).next_minor.text

old_poetry = Package("poetry", __version__)
old_poetry.add_dependency(Factory.create_dependency("cleo", "^0.8.2"))

new_poetry = Package("poetry", new_version)
new_poetry.add_dependency(Factory.create_dependency("cleo", "^1.0.0"))

installed_repository = Repository()
installed_repository.add_package(old_poetry)
installed_repository.add_package(Package("cleo", "0.8.2"))

repository = Repository()
repository.add_package(new_poetry)
repository.add_package(Package("cleo", "1.0.0"))

pool = Pool()
pool.add_repository(repository)

command._pool = pool

mocker.patch.object(InstalledRepository, "load", return_value=installed_repository)

tester.execute()

expected_output = """\
Updating Poetry to {}
Updating dependencies
Resolving dependencies...
Package operations: 0 installs, 2 updates, 0 removals
- Updating cleo (0.8.2 -> 1.0.0)
- Updating poetry ({} -> {})
Updating the poetry script
Poetry (1.2.0) is installed now. Great!
""".format(
new_version, __version__, new_version
)

assert tester.io.fetch_output() == expected_output

0 comments on commit 8fae35f

Please sign in to comment.