mirror of
https://github.com/python-poetry/install.python-poetry.org.git
synced 2024-11-24 23:10:57 -05:00
refactor: update path handling and comments
This is part of a major overhaul of the installer I hope to complete in a relatively short time. This first pass makes sure we use consistent directories on all platforms by preferring the documented paths to a possibly unexpected `site.getuserbase()` (which results in a Library path on macOS Framework builds, like Homebrew and OS-provided Pythons). Path handling as a whole is updated to take advantage of `pathlib` and DRY up some tedious code. It also starts the first stage of increasing use of classes and careful thought as to the visibility of class-level attributes in this script -- I hope to end up with a DSL-like end result that is as informative as possible to the reader of this script.
This commit is contained in:
parent
07dd8a55c5
commit
ac6fb50a38
1 changed files with 84 additions and 67 deletions
|
@ -1,17 +1,27 @@
|
||||||
"""
|
#!/usr/bin/env python3
|
||||||
This script will install Poetry and its dependencies.
|
r"""
|
||||||
|
This script will install Poetry and its dependencies in an isolated fashion.
|
||||||
|
|
||||||
It does, in order:
|
It will perform the following steps:
|
||||||
|
* Create a new virtual environment using the built-in venv module, or the virtualenv zipapp if venv is unavailable.
|
||||||
|
This will be created at a platform-specific path (or `$POETRY_HOME` if `$POETRY_HOME` is set:
|
||||||
|
- `~/Library/Application Support/pypoetry` on macOS
|
||||||
|
- `$XDG_DATA_HOME/pypoetry` on Linux/Unix (`$XDG_DATA_HOME` is `~/.local/share` if unset)
|
||||||
|
- `%APPDATA%\pypoetry` on Windows
|
||||||
|
* Update pip inside the virtual environment to avoid bugs in older versions.
|
||||||
|
* Install the latest (or a given) version of Poetry inside this virtual environment using pip.
|
||||||
|
* Install a `poetry` script into a platform-specific path (or `$POETRY_HOME/bin` if `$POETRY_HOME` is set):
|
||||||
|
- `~/.local/bin` on Unix
|
||||||
|
- `%APPDATA%\Python\Scripts` on Windows
|
||||||
|
* Attempt to inform the user if they need to add this bin directory to their `$PATH`, as well as how to do so.
|
||||||
|
* Upon failure, write an error log to `poetry-installer-error-<hash>.log and restore any previous environment.
|
||||||
|
|
||||||
- Creates a virtual environment using venv (or virtualenv zipapp) in the correct OS data dir which will be
|
This script performs minimal magic, and should be relatively stable. However, it is optimized for interactive developer
|
||||||
- `%APPDATA%\\pypoetry` on Windows
|
use and trivial pipelines. If you are considering using this script in production, you should consider manually-managed
|
||||||
- ~/Library/Application Support/pypoetry on MacOS
|
installs, or use of pipx as alternatives to executing arbitrary, unversioned code from the internet. If you prefer this
|
||||||
- `${XDG_DATA_HOME}/pypoetry` (or `~/.local/share/pypoetry` if it's not set) on UNIX systems
|
script to alternatives, consider maintaining a local copy as part of your infrastructure.
|
||||||
- In `${POETRY_HOME}` if it's set.
|
|
||||||
- Installs the latest or given version of Poetry inside this virtual environment.
|
For full documentation, visit https://python-poetry.org/docs/#installation.
|
||||||
- Installs a `poetry` script in the Python user directory (or `${POETRY_HOME/bin}` if `POETRY_HOME` is set).
|
|
||||||
- On failure, the error log is written to poetry-installer-error-*.log and any previously existing environment
|
|
||||||
is restored.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
@ -19,7 +29,6 @@ import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import site
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import sysconfig
|
import sysconfig
|
||||||
|
@ -134,38 +143,29 @@ def string_to_bool(value):
|
||||||
return value in {"true", "1", "y", "yes"}
|
return value in {"true", "1", "y", "yes"}
|
||||||
|
|
||||||
|
|
||||||
def data_dir(version: Optional[str] = None) -> Path:
|
def data_dir() -> Path:
|
||||||
if os.getenv("POETRY_HOME"):
|
if os.getenv("POETRY_HOME"):
|
||||||
return Path(os.getenv("POETRY_HOME")).expanduser()
|
return Path(os.getenv("POETRY_HOME")).expanduser()
|
||||||
|
|
||||||
if WINDOWS:
|
if WINDOWS:
|
||||||
const = "CSIDL_APPDATA"
|
base_dir = Path(_get_win_folder("CSIDL_APPDATA"))
|
||||||
path = os.path.normpath(_get_win_folder(const))
|
|
||||||
path = os.path.join(path, "pypoetry")
|
|
||||||
elif MACOS:
|
elif MACOS:
|
||||||
path = os.path.expanduser("~/Library/Application Support/pypoetry")
|
base_dir = Path("~/Library/Application Support").expanduser()
|
||||||
else:
|
else:
|
||||||
path = os.getenv("XDG_DATA_HOME", os.path.expanduser("~/.local/share"))
|
base_dir = Path(os.getenv("XDG_DATA_HOME", "~/.local/share")).expanduser()
|
||||||
path = os.path.join(path, "pypoetry")
|
|
||||||
|
|
||||||
if version:
|
base_dir = base_dir.resolve()
|
||||||
path = os.path.join(path, version)
|
return base_dir / "pypoetry"
|
||||||
|
|
||||||
return Path(path)
|
|
||||||
|
|
||||||
|
|
||||||
def bin_dir(version: Optional[str] = None) -> Path:
|
def bin_dir() -> Path:
|
||||||
if os.getenv("POETRY_HOME"):
|
if os.getenv("POETRY_HOME"):
|
||||||
return Path(os.getenv("POETRY_HOME"), "bin").expanduser()
|
return Path(os.getenv("POETRY_HOME")).expanduser() / "bin"
|
||||||
|
|
||||||
user_base = site.getuserbase()
|
|
||||||
|
|
||||||
if WINDOWS and not MINGW:
|
if WINDOWS and not MINGW:
|
||||||
bin_dir = os.path.join(user_base, "Scripts")
|
return Path(_get_win_folder("CSIDL_APPDATA")) / "Python/Scripts"
|
||||||
else:
|
else:
|
||||||
bin_dir = os.path.join(user_base, "bin")
|
return Path("~/.local/bin").expanduser()
|
||||||
|
|
||||||
return Path(bin_dir)
|
|
||||||
|
|
||||||
|
|
||||||
def _get_win_folder_from_registry(csidl_name):
|
def _get_win_folder_from_registry(csidl_name):
|
||||||
|
@ -181,9 +181,9 @@ def _get_win_folder_from_registry(csidl_name):
|
||||||
_winreg.HKEY_CURRENT_USER,
|
_winreg.HKEY_CURRENT_USER,
|
||||||
r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders",
|
r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders",
|
||||||
)
|
)
|
||||||
dir, type = _winreg.QueryValueEx(key, shell_folder_name)
|
path, _ = _winreg.QueryValueEx(key, shell_folder_name)
|
||||||
|
|
||||||
return dir
|
return path
|
||||||
|
|
||||||
|
|
||||||
def _get_win_folder_with_ctypes(csidl_name):
|
def _get_win_folder_with_ctypes(csidl_name):
|
||||||
|
@ -477,9 +477,26 @@ class Installer:
|
||||||
self._accept_all = accept_all
|
self._accept_all = accept_all
|
||||||
self._git = git
|
self._git = git
|
||||||
self._path = path
|
self._path = path
|
||||||
self._data_dir = data_dir()
|
|
||||||
self._bin_dir = bin_dir()
|
|
||||||
self._cursor = Cursor()
|
self._cursor = Cursor()
|
||||||
|
self._bin_dir = None
|
||||||
|
self._data_dir = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def bin_dir(self) -> Path:
|
||||||
|
if not self._bin_dir:
|
||||||
|
self._bin_dir = bin_dir()
|
||||||
|
return self._bin_dir
|
||||||
|
|
||||||
|
@property
|
||||||
|
def data_dir(self) -> Path:
|
||||||
|
if not self._data_dir:
|
||||||
|
self._data_dir = data_dir()
|
||||||
|
return self._data_dir
|
||||||
|
|
||||||
|
@property
|
||||||
|
def version_file(self) -> Path:
|
||||||
|
return self.data_dir.joinpath("VERSION")
|
||||||
|
|
||||||
def allows_prereleases(self) -> bool:
|
def allows_prereleases(self) -> bool:
|
||||||
return self._preview
|
return self._preview
|
||||||
|
@ -536,7 +553,7 @@ class Installer:
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def install(self, version, upgrade=False):
|
def install(self, version):
|
||||||
"""
|
"""
|
||||||
Installs Poetry in $POETRY_HOME.
|
Installs Poetry in $POETRY_HOME.
|
||||||
"""
|
"""
|
||||||
|
@ -549,13 +566,13 @@ class Installer:
|
||||||
with self.make_env(version) as env:
|
with self.make_env(version) as env:
|
||||||
self.install_poetry(version, env)
|
self.install_poetry(version, env)
|
||||||
self.make_bin(version, env)
|
self.make_bin(version, env)
|
||||||
self._data_dir.joinpath("VERSION").write_text(version)
|
self.version_file.write_text(version)
|
||||||
self._install_comment(version, "Done")
|
self._install_comment(version, "Done")
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def uninstall(self) -> int:
|
def uninstall(self) -> int:
|
||||||
if not self._data_dir.exists():
|
if not self.data_dir.exists():
|
||||||
self._write(
|
self._write(
|
||||||
"{} is not currently installed.".format(colorize("info", "Poetry"))
|
"{} is not currently installed.".format(colorize("info", "Poetry"))
|
||||||
)
|
)
|
||||||
|
@ -563,8 +580,8 @@ class Installer:
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
version = None
|
version = None
|
||||||
if self._data_dir.joinpath("VERSION").exists():
|
if self.version_file.exists():
|
||||||
version = self._data_dir.joinpath("VERSION").read_text().strip()
|
version = self.version_file.read_text().strip()
|
||||||
|
|
||||||
if version:
|
if version:
|
||||||
self._write(
|
self._write(
|
||||||
|
@ -575,10 +592,10 @@ class Installer:
|
||||||
else:
|
else:
|
||||||
self._write("Removing {}".format(colorize("info", "Poetry")))
|
self._write("Removing {}".format(colorize("info", "Poetry")))
|
||||||
|
|
||||||
shutil.rmtree(str(self._data_dir))
|
shutil.rmtree(str(self.data_dir))
|
||||||
for script in ["poetry", "poetry.bat", "poetry.exe"]:
|
for script in ["poetry", "poetry.bat", "poetry.exe"]:
|
||||||
if self._bin_dir.joinpath(script).exists():
|
if self.bin_dir.joinpath(script).exists():
|
||||||
self._bin_dir.joinpath(script).unlink()
|
self.bin_dir.joinpath(script).unlink()
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
@ -593,7 +610,7 @@ class Installer:
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def make_env(self, version: str) -> VirtualEnvironment:
|
def make_env(self, version: str) -> VirtualEnvironment:
|
||||||
env_path = self._data_dir.joinpath("venv")
|
env_path = self.data_dir.joinpath("venv")
|
||||||
env_path_saved = env_path.with_suffix(".save")
|
env_path_saved = env_path.with_suffix(".save")
|
||||||
|
|
||||||
if env_path.exists():
|
if env_path.exists():
|
||||||
|
@ -625,20 +642,20 @@ class Installer:
|
||||||
|
|
||||||
def make_bin(self, version: str, env: VirtualEnvironment) -> None:
|
def make_bin(self, version: str, env: VirtualEnvironment) -> None:
|
||||||
self._install_comment(version, "Creating script")
|
self._install_comment(version, "Creating script")
|
||||||
self._bin_dir.mkdir(parents=True, exist_ok=True)
|
self.bin_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
script = "poetry.exe" if WINDOWS else "poetry"
|
script = "poetry.exe" if WINDOWS else "poetry"
|
||||||
target_script = env.bin_path.joinpath(script)
|
target_script = env.bin_path.joinpath(script)
|
||||||
|
|
||||||
if self._bin_dir.joinpath(script).exists():
|
if self.bin_dir.joinpath(script).exists():
|
||||||
self._bin_dir.joinpath(script).unlink()
|
self.bin_dir.joinpath(script).unlink()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._bin_dir.joinpath(script).symlink_to(target_script)
|
self.bin_dir.joinpath(script).symlink_to(target_script)
|
||||||
except OSError:
|
except OSError:
|
||||||
# This can happen if the user
|
# This can happen if the user
|
||||||
# does not have the correct permission on Windows
|
# does not have the correct permission on Windows
|
||||||
shutil.copy(target_script, self._bin_dir.joinpath(script))
|
shutil.copy(target_script, self.bin_dir.joinpath(script))
|
||||||
|
|
||||||
def install_poetry(self, version: str, env: VirtualEnvironment) -> None:
|
def install_poetry(self, version: str, env: VirtualEnvironment) -> None:
|
||||||
self._install_comment(version, "Installing Poetry")
|
self._install_comment(version, "Installing Poetry")
|
||||||
|
@ -655,7 +672,7 @@ class Installer:
|
||||||
def display_pre_message(self) -> None:
|
def display_pre_message(self) -> None:
|
||||||
kwargs = {
|
kwargs = {
|
||||||
"poetry": colorize("info", "Poetry"),
|
"poetry": colorize("info", "Poetry"),
|
||||||
"poetry_home_bin": colorize("comment", self._bin_dir),
|
"poetry_home_bin": colorize("comment", self.bin_dir),
|
||||||
}
|
}
|
||||||
self._write(PRE_MESSAGE.format(**kwargs))
|
self._write(PRE_MESSAGE.format(**kwargs))
|
||||||
|
|
||||||
|
@ -672,17 +689,17 @@ class Installer:
|
||||||
path = self.get_windows_path_var()
|
path = self.get_windows_path_var()
|
||||||
|
|
||||||
message = POST_MESSAGE_NOT_IN_PATH
|
message = POST_MESSAGE_NOT_IN_PATH
|
||||||
if path and str(self._bin_dir) in path:
|
if path and str(self.bin_dir) in path:
|
||||||
message = POST_MESSAGE
|
message = POST_MESSAGE
|
||||||
|
|
||||||
self._write(
|
self._write(
|
||||||
message.format(
|
message.format(
|
||||||
poetry=colorize("info", "Poetry"),
|
poetry=colorize("info", "Poetry"),
|
||||||
version=colorize("b", version),
|
version=colorize("b", version),
|
||||||
poetry_home_bin=colorize("comment", self._bin_dir),
|
poetry_home_bin=colorize("comment", self.bin_dir),
|
||||||
poetry_executable=colorize("b", self._bin_dir.joinpath("poetry")),
|
poetry_executable=colorize("b", self.bin_dir.joinpath("poetry")),
|
||||||
configure_message=POST_MESSAGE_CONFIGURE_WINDOWS.format(
|
configure_message=POST_MESSAGE_CONFIGURE_WINDOWS.format(
|
||||||
poetry_home_bin=colorize("comment", self._bin_dir)
|
poetry_home_bin=colorize("comment", self.bin_dir)
|
||||||
),
|
),
|
||||||
test_command=colorize("b", "poetry --version"),
|
test_command=colorize("b", "poetry --version"),
|
||||||
)
|
)
|
||||||
|
@ -703,17 +720,17 @@ class Installer:
|
||||||
).decode("utf-8")
|
).decode("utf-8")
|
||||||
|
|
||||||
message = POST_MESSAGE_NOT_IN_PATH
|
message = POST_MESSAGE_NOT_IN_PATH
|
||||||
if fish_user_paths and str(self._bin_dir) in fish_user_paths:
|
if fish_user_paths and str(self.bin_dir) in fish_user_paths:
|
||||||
message = POST_MESSAGE
|
message = POST_MESSAGE
|
||||||
|
|
||||||
self._write(
|
self._write(
|
||||||
message.format(
|
message.format(
|
||||||
poetry=colorize("info", "Poetry"),
|
poetry=colorize("info", "Poetry"),
|
||||||
version=colorize("b", version),
|
version=colorize("b", version),
|
||||||
poetry_home_bin=colorize("comment", self._bin_dir),
|
poetry_home_bin=colorize("comment", self.bin_dir),
|
||||||
poetry_executable=colorize("b", self._bin_dir.joinpath("poetry")),
|
poetry_executable=colorize("b", self.bin_dir.joinpath("poetry")),
|
||||||
configure_message=POST_MESSAGE_CONFIGURE_FISH.format(
|
configure_message=POST_MESSAGE_CONFIGURE_FISH.format(
|
||||||
poetry_home_bin=colorize("comment", self._bin_dir)
|
poetry_home_bin=colorize("comment", self.bin_dir)
|
||||||
),
|
),
|
||||||
test_command=colorize("b", "poetry --version"),
|
test_command=colorize("b", "poetry --version"),
|
||||||
)
|
)
|
||||||
|
@ -723,30 +740,30 @@ class Installer:
|
||||||
paths = os.getenv("PATH", "").split(":")
|
paths = os.getenv("PATH", "").split(":")
|
||||||
|
|
||||||
message = POST_MESSAGE_NOT_IN_PATH
|
message = POST_MESSAGE_NOT_IN_PATH
|
||||||
if paths and str(self._bin_dir) in paths:
|
if paths and str(self.bin_dir) in paths:
|
||||||
message = POST_MESSAGE
|
message = POST_MESSAGE
|
||||||
|
|
||||||
self._write(
|
self._write(
|
||||||
message.format(
|
message.format(
|
||||||
poetry=colorize("info", "Poetry"),
|
poetry=colorize("info", "Poetry"),
|
||||||
version=colorize("b", version),
|
version=colorize("b", version),
|
||||||
poetry_home_bin=colorize("comment", self._bin_dir),
|
poetry_home_bin=colorize("comment", self.bin_dir),
|
||||||
poetry_executable=colorize("b", self._bin_dir.joinpath("poetry")),
|
poetry_executable=colorize("b", self.bin_dir.joinpath("poetry")),
|
||||||
configure_message=POST_MESSAGE_CONFIGURE_UNIX.format(
|
configure_message=POST_MESSAGE_CONFIGURE_UNIX.format(
|
||||||
poetry_home_bin=colorize("comment", self._bin_dir)
|
poetry_home_bin=colorize("comment", self.bin_dir)
|
||||||
),
|
),
|
||||||
test_command=colorize("b", "poetry --version"),
|
test_command=colorize("b", "poetry --version"),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def ensure_directories(self) -> None:
|
def ensure_directories(self) -> None:
|
||||||
self._data_dir.mkdir(parents=True, exist_ok=True)
|
self.data_dir.mkdir(parents=True, exist_ok=True)
|
||||||
self._bin_dir.mkdir(parents=True, exist_ok=True)
|
self.bin_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
def get_version(self):
|
def get_version(self):
|
||||||
current_version = None
|
current_version = None
|
||||||
if self._data_dir.joinpath("VERSION").exists():
|
if self.version_file.exists():
|
||||||
current_version = self._data_dir.joinpath("VERSION").read_text().strip()
|
current_version = self.version_file.read_text().strip()
|
||||||
|
|
||||||
self._write(colorize("info", "Retrieving Poetry metadata"))
|
self._write(colorize("info", "Retrieving Poetry metadata"))
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue