Skip to content

Commit c168c1d

Browse files
committed
Ensure the self update command is compatible with the new installer
1 parent 936392d commit c168c1d

File tree

3 files changed

+211
-8
lines changed

3 files changed

+211
-8
lines changed

poetry/console/commands/self/update.py

+137-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import os
55
import re
66
import shutil
7+
import site
78
import stat
89
import subprocess
910
import sys
@@ -16,6 +17,7 @@
1617
from cleo import option
1718

1819
from poetry.core.packages import Dependency
20+
from poetry.utils._compat import Path
1921

2022
from ..command import Command
2123

@@ -60,6 +62,10 @@ class SelfUpdateCommand(Command):
6062
REPOSITORY_URL = "https://github.com/python-poetry/poetry"
6163
BASE_URL = REPOSITORY_URL + "/releases/download"
6264

65+
_data_dir = None
66+
_bin_dir = None
67+
_pool = None
68+
6369
@property
6470
def home(self):
6571
from poetry.utils._compat import Path
@@ -78,18 +84,75 @@ def lib(self):
7884
def lib_backup(self):
7985
return self.home / "lib-backup"
8086

87+
@property
88+
def data_dir(self): # type: () -> Path
89+
if self._data_dir is not None:
90+
return self._data_dir
91+
92+
from poetry.locations import data_dir
93+
94+
self._data_dir = data_dir()
95+
96+
return self._data_dir
97+
98+
@property
99+
def bin_dir(self): # type: () -> Path
100+
if self._data_dir is not None:
101+
return self._data_dir
102+
103+
from poetry.utils._compat import WINDOWS
104+
105+
if os.getenv("POETRY_HOME"):
106+
return Path(os.getenv("POETRY_HOME"), "bin").expanduser()
107+
108+
user_base = site.getuserbase()
109+
110+
if WINDOWS:
111+
bin_dir = os.path.join(user_base, "Scripts")
112+
else:
113+
bin_dir = os.path.join(user_base, "bin")
114+
115+
self._bin_dir = Path(bin_dir)
116+
117+
return self._bin_dir
118+
119+
@property
120+
def pool(self):
121+
if self._pool is not None:
122+
return self._pool
123+
124+
from poetry.repositories.pool import Pool
125+
from poetry.repositories.pypi_repository import PyPiRepository
126+
127+
pool = Pool()
128+
pool.add_repository(PyPiRepository(fallback=False))
129+
130+
self._pool = pool
131+
132+
return self._pool
133+
81134
def handle(self):
82135
from poetry.__version__ import __version__
83136
from poetry.core.semver import Version
84-
from poetry.repositories.pypi_repository import PyPiRepository
137+
from poetry.utils.env import EnvManager
85138

86-
self._check_recommended_installation()
139+
new_update_method = False
140+
try:
141+
self._check_recommended_installation()
142+
except RuntimeError as e:
143+
env = EnvManager.get_system_env(naive=True)
144+
try:
145+
env.path.relative_to(self.data_dir)
146+
except ValueError:
147+
raise e
148+
149+
new_update_method = True
87150

88151
version = self.argument("version")
89152
if not version:
90153
version = ">=" + __version__
91154

92-
repo = PyPiRepository(fallback=False)
155+
repo = self.pool.repositories[0]
93156
packages = repo.find_packages(
94157
Dependency("poetry", version, allows_prereleases=self.option("preview"))
95158
)
@@ -127,6 +190,9 @@ def handle(self):
127190
self.line("You are using the latest version")
128191
return
129192

193+
if new_update_method:
194+
return self.update_with_new_method(release.version)
195+
130196
self.update(release)
131197

132198
def update(self, release):
@@ -165,6 +231,18 @@ def update(self, release):
165231
)
166232
)
167233

234+
def update_with_new_method(self, version):
235+
self.line("Updating <c1>Poetry</c1> to <c2>{}</c2>".format(version))
236+
self.line("")
237+
238+
self._update_with_new_method(version)
239+
self._make_bin()
240+
241+
self.line("")
242+
self.line(
243+
"<c1>Poetry</c1> (<c2>{}</c2>) is installed now. Great!".format(version)
244+
)
245+
168246
def _update(self, version):
169247
from poetry.utils.helpers import temporary_directory
170248

@@ -235,6 +313,62 @@ def _update(self, version):
235313
finally:
236314
gz.close()
237315

316+
def _update_with_new_method(self, version):
317+
from poetry.config.config import Config
318+
from poetry.core.packages.dependency import Dependency
319+
from poetry.core.packages.project_package import ProjectPackage
320+
from poetry.installation.installer import Installer
321+
from poetry.packages.locker import NullLocker
322+
from poetry.repositories.installed_repository import InstalledRepository
323+
from poetry.utils.env import EnvManager
324+
325+
env = EnvManager.get_system_env()
326+
installed = InstalledRepository.load(env)
327+
328+
root = ProjectPackage("poetry-updater", "0.0.0")
329+
root.python_versions = ".".join(str(c) for c in env.version_info[:3])
330+
root.add_dependency(Dependency("poetry", version.text))
331+
332+
installer = Installer(
333+
self.io,
334+
env,
335+
root,
336+
NullLocker(self.data_dir.joinpath("poetry.lock"), {}),
337+
self.pool,
338+
Config(),
339+
installed=installed,
340+
)
341+
installer.update(True)
342+
installer.run()
343+
344+
def _make_bin(self):
345+
from poetry.utils._compat import WINDOWS
346+
347+
self.line("")
348+
self.line("Updating the <c1>poetry</c1> script")
349+
350+
self.bin_dir.mkdir(parents=True, exist_ok=True)
351+
352+
script = "poetry"
353+
target_script = "venv/bin/poetry"
354+
if WINDOWS:
355+
script = "poetry.exe"
356+
target_script = "venv/Scripts/poetry.exe"
357+
358+
if self.bin_dir.joinpath(script).exists():
359+
self.bin_dir.joinpath(script).unlink()
360+
361+
try:
362+
self.bin_dir.joinpath(script).symlink_to(
363+
self.data_dir.joinpath(target_script)
364+
)
365+
except OSError:
366+
# This can happen if the user
367+
# does not have the correct permission on Windows
368+
shutil.copy(
369+
self.data_dir.joinpath(target_script), self.bin_dir.joinpath(script)
370+
)
371+
238372
def process(self, *args):
239373
return subprocess.check_output(list(args), stderr=subprocess.STDOUT)
240374

poetry/packages/locker.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,9 @@ def get_project_dependency_packages(
348348
if extra_package_names is not None:
349349
extra_package_names = set(
350350
get_extra_package_names(
351-
repository.packages, self.lock_data.get("extras", {}), extras or (),
351+
repository.packages,
352+
self.lock_data.get("extras", {}),
353+
extras or (),
352354
)
353355
)
354356

@@ -586,3 +588,8 @@ def _dump_package(self, package): # type: (Package) -> dict
586588
data["develop"] = package.develop
587589

588590
return data
591+
592+
593+
class NullLocker(Locker):
594+
def set_lock_data(self, root, packages): # type: (Package, List[Package]) -> None
595+
pass

tests/console/commands/self/test_update.py

+66-4
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,13 @@
55
from poetry.__version__ import __version__
66
from poetry.core.packages.package import Package
77
from poetry.core.semver.version import Version
8+
from poetry.factory import Factory
9+
from poetry.repositories.installed_repository import InstalledRepository
10+
from poetry.repositories.pool import Pool
11+
from poetry.repositories.repository import Repository
812
from poetry.utils._compat import WINDOWS
913
from poetry.utils._compat import Path
14+
from poetry.utils.env import EnvManager
1015

1116

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

2732
version = Version.parse(__version__).next_minor.text
28-
mocker.patch(
29-
"poetry.repositories.pypi_repository.PyPiRepository.find_packages",
30-
return_value=[Package("poetry", version)],
31-
)
33+
repository = Repository()
34+
repository.add_package(Package("poetry", version))
35+
36+
pool = Pool()
37+
pool.add_repository(repository)
38+
39+
command._pool = pool
3240
mocker.patch.object(command, "_check_recommended_installation", return_value=None)
3341
mocker.patch.object(
3442
command, "_get_release_name", return_value="poetry-{}-darwin".format(version)
@@ -89,3 +97,57 @@ def test_self_update_should_install_all_necessary_elements(
8997

9098
assert lib.exists()
9199
assert lib.joinpath("poetry").exists()
100+
101+
102+
def test_self_update_can_update_from_recommended_installation(
103+
tester, http, mocker, environ, tmp_venv
104+
):
105+
mocker.patch.object(EnvManager, "get_system_env", return_value=tmp_venv)
106+
107+
command = tester._command
108+
command._data_dir = tmp_venv.path.parent
109+
110+
new_version = Version.parse(__version__).next_minor.text
111+
112+
old_poetry = Package("poetry", __version__)
113+
old_poetry.add_dependency(Factory.create_dependency("cleo", "^0.8.2"))
114+
115+
new_poetry = Package("poetry", new_version)
116+
new_poetry.add_dependency(Factory.create_dependency("cleo", "^1.0.0"))
117+
118+
installed_repository = Repository()
119+
installed_repository.add_package(old_poetry)
120+
installed_repository.add_package(Package("cleo", "0.8.2"))
121+
122+
repository = Repository()
123+
repository.add_package(new_poetry)
124+
repository.add_package(Package("cleo", "1.0.0"))
125+
126+
pool = Pool()
127+
pool.add_repository(repository)
128+
129+
command._pool = pool
130+
131+
mocker.patch.object(InstalledRepository, "load", return_value=installed_repository)
132+
133+
tester.execute()
134+
135+
expected_output = """\
136+
Updating Poetry to {}
137+
138+
Updating dependencies
139+
Resolving dependencies...
140+
141+
Package operations: 0 installs, 2 updates, 0 removals
142+
143+
- Updating cleo (0.8.2 -> 1.0.0)
144+
- Updating poetry ({} -> {})
145+
146+
Updating the poetry script
147+
148+
Poetry (1.2.0) is installed now. Great!
149+
""".format(
150+
new_version, __version__, new_version
151+
)
152+
153+
assert tester.io.fetch_output() == expected_output

0 commit comments

Comments
 (0)