mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-15 11:53:49 +07:00
PY-65295 Poetry installation
Try to install Poetry and Pipenv if they are not found Merge-request: IJ-MR-141839 Merged-by: Egor Eliseev <Egor.Eliseev@jetbrains.com> GitOrigin-RevId: 535426090df23b358ba61a9e21c2f0954c201945
This commit is contained in:
committed by
intellij-monorepo-bot
parent
9f592f91ee
commit
a54292035d
945
python/helpers/pycharm_poetry_installer.py
Normal file
945
python/helpers/pycharm_poetry_installer.py
Normal file
@@ -0,0 +1,945 @@
|
||||
#!/usr/bin/env python3
|
||||
r"""
|
||||
This script will install Poetry and its dependencies in an isolated fashion.
|
||||
|
||||
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.
|
||||
|
||||
This script performs minimal magic, and should be relatively stable. However, it is optimized for interactive developer
|
||||
use and trivial pipelines. If you are considering using this script in production, you should consider manually-managed
|
||||
installs, or use of pipx as alternatives to executing arbitrary, unversioned code from the internet. If you prefer this
|
||||
script to alternatives, consider maintaining a local copy as part of your infrastructure.
|
||||
|
||||
For full documentation, visit https://python-poetry.org/docs/#installation.
|
||||
""" # noqa: E501
|
||||
import sys
|
||||
|
||||
|
||||
# Eager version check so we fail nicely before possible syntax errors
|
||||
if sys.version_info < (3, 6): # noqa: UP036
|
||||
sys.stdout.write("Poetry installer requires Python 3.6 or newer to run!\n")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sysconfig
|
||||
import tempfile
|
||||
|
||||
from contextlib import closing
|
||||
from contextlib import contextmanager
|
||||
from functools import cmp_to_key
|
||||
from io import UnsupportedOperation
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from urllib.request import Request
|
||||
from urllib.request import urlopen
|
||||
|
||||
|
||||
SHELL = os.getenv("SHELL", "")
|
||||
WINDOWS = sys.platform.startswith("win") or (sys.platform == "cli" and os.name == "nt")
|
||||
MINGW = sysconfig.get_platform().startswith("mingw")
|
||||
MACOS = sys.platform == "darwin"
|
||||
|
||||
FOREGROUND_COLORS = {
|
||||
"black": 30,
|
||||
"red": 31,
|
||||
"green": 32,
|
||||
"yellow": 33,
|
||||
"blue": 34,
|
||||
"magenta": 35,
|
||||
"cyan": 36,
|
||||
"white": 37,
|
||||
}
|
||||
|
||||
BACKGROUND_COLORS = {
|
||||
"black": 40,
|
||||
"red": 41,
|
||||
"green": 42,
|
||||
"yellow": 43,
|
||||
"blue": 44,
|
||||
"magenta": 45,
|
||||
"cyan": 46,
|
||||
"white": 47,
|
||||
}
|
||||
|
||||
OPTIONS = {"bold": 1, "underscore": 4, "blink": 5, "reverse": 7, "conceal": 8}
|
||||
|
||||
|
||||
def style(fg, bg, options):
|
||||
codes = []
|
||||
|
||||
if fg:
|
||||
codes.append(FOREGROUND_COLORS[fg])
|
||||
|
||||
if bg:
|
||||
codes.append(BACKGROUND_COLORS[bg])
|
||||
|
||||
if options:
|
||||
if not isinstance(options, (list, tuple)):
|
||||
options = [options]
|
||||
|
||||
for option in options:
|
||||
codes.append(OPTIONS[option])
|
||||
|
||||
return "\033[{}m".format(";".join(map(str, codes)))
|
||||
|
||||
|
||||
STYLES = {
|
||||
"info": style("cyan", None, None),
|
||||
"comment": style("yellow", None, None),
|
||||
"success": style("green", None, None),
|
||||
"error": style("red", None, None),
|
||||
"warning": style("yellow", None, None),
|
||||
"b": style(None, None, ("bold",)),
|
||||
}
|
||||
|
||||
|
||||
def is_decorated():
|
||||
if WINDOWS:
|
||||
return (
|
||||
os.getenv("ANSICON") is not None
|
||||
or os.getenv("ConEmuANSI") == "ON" # noqa: SIM112
|
||||
or os.getenv("Term") == "xterm" # noqa: SIM112
|
||||
)
|
||||
|
||||
if not hasattr(sys.stdout, "fileno"):
|
||||
return False
|
||||
|
||||
try:
|
||||
return os.isatty(sys.stdout.fileno())
|
||||
except UnsupportedOperation:
|
||||
return False
|
||||
|
||||
|
||||
def is_interactive():
|
||||
if not hasattr(sys.stdin, "fileno"):
|
||||
return False
|
||||
|
||||
try:
|
||||
return os.isatty(sys.stdin.fileno())
|
||||
except UnsupportedOperation:
|
||||
return False
|
||||
|
||||
|
||||
def colorize(style, text):
|
||||
if not is_decorated():
|
||||
return text
|
||||
|
||||
return f"{STYLES[style]}{text}\033[0m"
|
||||
|
||||
|
||||
def string_to_bool(value):
|
||||
value = value.lower()
|
||||
|
||||
return value in {"true", "1", "y", "yes"}
|
||||
|
||||
|
||||
def data_dir() -> Path:
|
||||
if os.getenv("POETRY_HOME"):
|
||||
return Path(os.getenv("POETRY_HOME")).expanduser()
|
||||
|
||||
if WINDOWS:
|
||||
base_dir = Path(_get_win_folder("CSIDL_APPDATA"))
|
||||
elif MACOS:
|
||||
base_dir = Path("~/Library/Application Support").expanduser()
|
||||
else:
|
||||
base_dir = Path(os.getenv("XDG_DATA_HOME", "~/.local/share")).expanduser()
|
||||
|
||||
base_dir = base_dir.resolve()
|
||||
return base_dir / "pypoetry"
|
||||
|
||||
|
||||
def bin_dir() -> Path:
|
||||
if os.getenv("POETRY_HOME"):
|
||||
return Path(os.getenv("POETRY_HOME")).expanduser() / "bin"
|
||||
|
||||
if WINDOWS and not MINGW:
|
||||
return Path(_get_win_folder("CSIDL_APPDATA")) / "Python/Scripts"
|
||||
else:
|
||||
return Path("~/.local/bin").expanduser()
|
||||
|
||||
|
||||
def _get_win_folder_from_registry(csidl_name):
|
||||
import winreg as _winreg
|
||||
|
||||
shell_folder_name = {
|
||||
"CSIDL_APPDATA": "AppData",
|
||||
"CSIDL_COMMON_APPDATA": "Common AppData",
|
||||
"CSIDL_LOCAL_APPDATA": "Local AppData",
|
||||
}[csidl_name]
|
||||
|
||||
key = _winreg.OpenKey(
|
||||
_winreg.HKEY_CURRENT_USER,
|
||||
r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders",
|
||||
)
|
||||
path, _ = _winreg.QueryValueEx(key, shell_folder_name)
|
||||
|
||||
return path
|
||||
|
||||
|
||||
def _get_win_folder_with_ctypes(csidl_name):
|
||||
import ctypes
|
||||
|
||||
csidl_const = {
|
||||
"CSIDL_APPDATA": 26,
|
||||
"CSIDL_COMMON_APPDATA": 35,
|
||||
"CSIDL_LOCAL_APPDATA": 28,
|
||||
}[csidl_name]
|
||||
|
||||
buf = ctypes.create_unicode_buffer(1024)
|
||||
ctypes.windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf)
|
||||
|
||||
# Downgrade to short path name if have highbit chars. See
|
||||
# <http://bugs.activestate.com/show_bug.cgi?id=85099>.
|
||||
has_high_char = False
|
||||
for c in buf:
|
||||
if ord(c) > 255:
|
||||
has_high_char = True
|
||||
break
|
||||
if has_high_char:
|
||||
buf2 = ctypes.create_unicode_buffer(1024)
|
||||
if ctypes.windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024):
|
||||
buf = buf2
|
||||
|
||||
return buf.value
|
||||
|
||||
|
||||
if WINDOWS:
|
||||
try:
|
||||
from ctypes import windll # noqa: F401
|
||||
|
||||
_get_win_folder = _get_win_folder_with_ctypes
|
||||
except ImportError:
|
||||
_get_win_folder = _get_win_folder_from_registry
|
||||
|
||||
|
||||
PRE_MESSAGE = """# Welcome to {poetry}!
|
||||
|
||||
This will download and install the latest version of {poetry},
|
||||
a dependency and package manager for Python.
|
||||
|
||||
It will add the `poetry` command to {poetry}'s bin directory, located at:
|
||||
|
||||
{poetry_home_bin}
|
||||
|
||||
You can uninstall at any time by executing this script with the --uninstall option,
|
||||
and these changes will be reverted.
|
||||
"""
|
||||
|
||||
POST_MESSAGE = """{poetry} ({version}) is installed now. Great!
|
||||
|
||||
You can test that everything is set up by executing:
|
||||
|
||||
`{test_command}`
|
||||
"""
|
||||
|
||||
POST_MESSAGE_NOT_IN_PATH = """{poetry} ({version}) is installed now. Great!
|
||||
|
||||
To get started you need {poetry}'s bin directory ({poetry_home_bin}) in your `PATH`
|
||||
environment variable.
|
||||
{configure_message}
|
||||
Alternatively, you can call {poetry} explicitly with `{poetry_executable}`.
|
||||
|
||||
You can test that everything is set up by executing:
|
||||
|
||||
`{test_command}`
|
||||
"""
|
||||
|
||||
POST_MESSAGE_CONFIGURE_UNIX = """
|
||||
Add `export PATH="{poetry_home_bin}:$PATH"` to your shell configuration file.
|
||||
"""
|
||||
|
||||
POST_MESSAGE_CONFIGURE_FISH = """
|
||||
You can execute `set -U fish_user_paths {poetry_home_bin} $fish_user_paths`
|
||||
"""
|
||||
|
||||
POST_MESSAGE_CONFIGURE_WINDOWS = """"""
|
||||
|
||||
|
||||
class PoetryInstallationError(RuntimeError):
|
||||
def __init__(self, return_code: int = 0, log: Optional[str] = None):
|
||||
super().__init__()
|
||||
self.return_code = return_code
|
||||
self.log = log
|
||||
|
||||
|
||||
class VirtualEnvironment:
|
||||
def __init__(self, path: Path) -> None:
|
||||
self._path = path
|
||||
self._bin_path = self._path.joinpath(
|
||||
"Scripts" if WINDOWS and not MINGW else "bin"
|
||||
)
|
||||
# str is for compatibility with subprocess.run on CPython <= 3.7 on Windows
|
||||
self._python = str(
|
||||
self._path.joinpath(self._bin_path, "python.exe" if WINDOWS else "python")
|
||||
)
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
return self._path
|
||||
|
||||
@property
|
||||
def bin_path(self):
|
||||
return self._bin_path
|
||||
|
||||
@classmethod
|
||||
def make(cls, target: Path) -> "VirtualEnvironment":
|
||||
if not sys.executable:
|
||||
raise ValueError(
|
||||
"Unable to determine sys.executable. Set PATH to a sane value or set it"
|
||||
" explicitly with PYTHONEXECUTABLE."
|
||||
)
|
||||
|
||||
try:
|
||||
# on some linux distributions (eg: debian), the distribution provided python
|
||||
# installation might not include ensurepip, causing the venv module to
|
||||
# fail when attempting to create a virtual environment
|
||||
# we import ensurepip but do not use it explicitly here
|
||||
import ensurepip # noqa: F401
|
||||
import venv
|
||||
|
||||
builder = venv.EnvBuilder(clear=True, with_pip=True, symlinks=False)
|
||||
context = builder.ensure_directories(target)
|
||||
|
||||
if (
|
||||
WINDOWS
|
||||
and hasattr(context, "env_exec_cmd")
|
||||
and context.env_exe != context.env_exec_cmd
|
||||
):
|
||||
target = target.resolve()
|
||||
|
||||
builder.create(target)
|
||||
except ImportError:
|
||||
# fallback to using virtualenv package if venv is not available, eg: ubuntu
|
||||
python_version = f"{sys.version_info.major}.{sys.version_info.minor}"
|
||||
virtualenv_bootstrap_url = (
|
||||
f"https://bootstrap.pypa.io/virtualenv/{python_version}/virtualenv.pyz"
|
||||
)
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix="poetry-installer") as temp_dir:
|
||||
virtualenv_pyz = Path(temp_dir) / "virtualenv.pyz"
|
||||
request = Request(
|
||||
virtualenv_bootstrap_url, headers={"User-Agent": "Python Poetry"}
|
||||
)
|
||||
virtualenv_pyz.write_bytes(urlopen(request).read())
|
||||
cls.run(
|
||||
sys.executable, virtualenv_pyz, "--clear", "--always-copy", target
|
||||
)
|
||||
|
||||
# We add a special file so that Poetry can detect
|
||||
# its own virtual environment
|
||||
target.joinpath("poetry_env").touch()
|
||||
|
||||
env = cls(target)
|
||||
|
||||
# this ensures that outdated system default pip does not trigger older bugs
|
||||
env.pip("install", "--disable-pip-version-check", "--upgrade", "pip")
|
||||
|
||||
return env
|
||||
|
||||
@staticmethod
|
||||
def run(*args, **kwargs) -> subprocess.CompletedProcess:
|
||||
completed_process = subprocess.run(
|
||||
args,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
**kwargs,
|
||||
)
|
||||
if completed_process.returncode != 0:
|
||||
raise PoetryInstallationError(
|
||||
return_code=completed_process.returncode,
|
||||
log=completed_process.stdout.decode(),
|
||||
)
|
||||
return completed_process
|
||||
|
||||
def python(self, *args, **kwargs) -> subprocess.CompletedProcess:
|
||||
return self.run(self._python, *args, **kwargs)
|
||||
|
||||
def pip(self, *args, **kwargs) -> subprocess.CompletedProcess:
|
||||
return self.python("-m", "pip", *args, **kwargs)
|
||||
|
||||
|
||||
class Cursor:
|
||||
def __init__(self) -> None:
|
||||
self._output = sys.stdout
|
||||
|
||||
def move_up(self, lines: int = 1) -> "Cursor":
|
||||
self._output.write(f"\x1b[{lines}A")
|
||||
|
||||
return self
|
||||
|
||||
def move_down(self, lines: int = 1) -> "Cursor":
|
||||
self._output.write(f"\x1b[{lines}B")
|
||||
|
||||
return self
|
||||
|
||||
def move_right(self, columns: int = 1) -> "Cursor":
|
||||
self._output.write(f"\x1b[{columns}C")
|
||||
|
||||
return self
|
||||
|
||||
def move_left(self, columns: int = 1) -> "Cursor":
|
||||
self._output.write(f"\x1b[{columns}D")
|
||||
|
||||
return self
|
||||
|
||||
def move_to_column(self, column: int) -> "Cursor":
|
||||
self._output.write(f"\x1b[{column}G")
|
||||
|
||||
return self
|
||||
|
||||
def move_to_position(self, column: int, row: int) -> "Cursor":
|
||||
self._output.write(f"\x1b[{row + 1};{column}H")
|
||||
|
||||
return self
|
||||
|
||||
def save_position(self) -> "Cursor":
|
||||
self._output.write("\x1b7")
|
||||
|
||||
return self
|
||||
|
||||
def restore_position(self) -> "Cursor":
|
||||
self._output.write("\x1b8")
|
||||
|
||||
return self
|
||||
|
||||
def hide(self) -> "Cursor":
|
||||
self._output.write("\x1b[?25l")
|
||||
|
||||
return self
|
||||
|
||||
def show(self) -> "Cursor":
|
||||
self._output.write("\x1b[?25h\x1b[?0c")
|
||||
|
||||
return self
|
||||
|
||||
def clear_line(self) -> "Cursor":
|
||||
"""
|
||||
Clears all the output from the current line.
|
||||
"""
|
||||
self._output.write("\x1b[2K")
|
||||
|
||||
return self
|
||||
|
||||
def clear_line_after(self) -> "Cursor":
|
||||
"""
|
||||
Clears all the output from the current line after the current position.
|
||||
"""
|
||||
self._output.write("\x1b[K")
|
||||
|
||||
return self
|
||||
|
||||
def clear_output(self) -> "Cursor":
|
||||
"""
|
||||
Clears all the output from the cursors' current position
|
||||
to the end of the screen.
|
||||
"""
|
||||
self._output.write("\x1b[0J")
|
||||
|
||||
return self
|
||||
|
||||
def clear_screen(self) -> "Cursor":
|
||||
"""
|
||||
Clears the entire screen.
|
||||
"""
|
||||
self._output.write("\x1b[2J")
|
||||
|
||||
return self
|
||||
|
||||
|
||||
class Installer:
|
||||
METADATA_URL = "https://pypi.org/pypi/poetry/json"
|
||||
VERSION_REGEX = re.compile(
|
||||
r"v?(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:\.(\d+))?"
|
||||
"("
|
||||
"[._-]?"
|
||||
r"(?:(stable|beta|b|rc|RC|alpha|a|patch|pl|p)((?:[.-]?\d+)*)?)?"
|
||||
"([.-]?dev)?"
|
||||
")?"
|
||||
r"(?:\+[^\s]+)?"
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
version: Optional[str] = None,
|
||||
preview: bool = False,
|
||||
force: bool = False,
|
||||
accept_all: bool = False,
|
||||
git: Optional[str] = None,
|
||||
path: Optional[str] = None,
|
||||
) -> None:
|
||||
self._version = version
|
||||
self._preview = preview
|
||||
self._force = force
|
||||
self._accept_all = accept_all
|
||||
self._git = git
|
||||
self._path = path
|
||||
|
||||
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:
|
||||
return self._preview
|
||||
|
||||
def run(self) -> int:
|
||||
if self._git:
|
||||
version = self._git
|
||||
elif self._path:
|
||||
version = self._path
|
||||
else:
|
||||
try:
|
||||
version, current_version = self.get_version()
|
||||
except ValueError:
|
||||
return 1
|
||||
|
||||
if version is None:
|
||||
return 0
|
||||
|
||||
self.display_pre_message()
|
||||
self.ensure_directories()
|
||||
|
||||
def _is_self_upgrade_supported(x):
|
||||
mx = self.VERSION_REGEX.match(x)
|
||||
|
||||
if mx is None:
|
||||
# the version is not semver, perhaps scm or file
|
||||
# we assume upgrade is supported
|
||||
return True
|
||||
|
||||
vx = (*tuple(int(p) for p in mx.groups()[:3]), mx.group(5))
|
||||
return vx >= (1, 1, 7)
|
||||
|
||||
if version and not _is_self_upgrade_supported(version):
|
||||
self._write(
|
||||
colorize(
|
||||
"warning",
|
||||
f"You are installing {version}. When using the current installer, "
|
||||
"this version does not support updating using the 'self update' "
|
||||
"command. Please use 1.1.7 or later.",
|
||||
)
|
||||
)
|
||||
if not self._accept_all:
|
||||
continue_install = input("Do you want to continue? ([y]/n) ") or "y"
|
||||
if continue_install.lower() in {"n", "no"}:
|
||||
return 0
|
||||
|
||||
try:
|
||||
self.install(version)
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise PoetryInstallationError(
|
||||
return_code=e.returncode, log=e.output.decode()
|
||||
) from e
|
||||
|
||||
self._write("")
|
||||
self.display_post_message(version)
|
||||
|
||||
return 0
|
||||
|
||||
def install(self, version):
|
||||
"""
|
||||
Installs Poetry in $POETRY_HOME.
|
||||
"""
|
||||
self._write(
|
||||
"Installing {} ({})".format(
|
||||
colorize("info", "Poetry"), colorize("info", version)
|
||||
)
|
||||
)
|
||||
|
||||
with self.make_env(version) as env:
|
||||
self.install_poetry(version, env)
|
||||
self.make_bin(version, env)
|
||||
self.version_file.write_text(version)
|
||||
self._install_comment(version, "Done")
|
||||
|
||||
return 0
|
||||
|
||||
def uninstall(self) -> int:
|
||||
if not self.data_dir.exists():
|
||||
self._write(
|
||||
"{} is not currently installed.".format(colorize("info", "Poetry"))
|
||||
)
|
||||
|
||||
return 1
|
||||
|
||||
version = None
|
||||
if self.version_file.exists():
|
||||
version = self.version_file.read_text().strip()
|
||||
|
||||
if version:
|
||||
self._write(
|
||||
"Removing {} ({})".format(
|
||||
colorize("info", "Poetry"), colorize("b", version)
|
||||
)
|
||||
)
|
||||
else:
|
||||
self._write("Removing {}".format(colorize("info", "Poetry")))
|
||||
|
||||
shutil.rmtree(str(self.data_dir))
|
||||
for script in ["poetry", "poetry.bat", "poetry.exe"]:
|
||||
if self.bin_dir.joinpath(script).exists():
|
||||
self.bin_dir.joinpath(script).unlink()
|
||||
|
||||
return 0
|
||||
|
||||
def _install_comment(self, version: str, message: str):
|
||||
self._overwrite(
|
||||
"Installing {} ({}): {}".format(
|
||||
colorize("info", "Poetry"),
|
||||
colorize("b", version),
|
||||
colorize("comment", message),
|
||||
)
|
||||
)
|
||||
|
||||
@contextmanager
|
||||
def make_env(self, version: str) -> VirtualEnvironment:
|
||||
env_path = self.data_dir.joinpath("venv")
|
||||
env_path_saved = env_path.with_suffix(".save")
|
||||
|
||||
if env_path.exists():
|
||||
self._install_comment(version, "Saving existing environment")
|
||||
if env_path_saved.exists():
|
||||
shutil.rmtree(env_path_saved)
|
||||
shutil.move(env_path, env_path_saved)
|
||||
|
||||
try:
|
||||
self._install_comment(version, "Creating environment")
|
||||
yield VirtualEnvironment.make(env_path)
|
||||
except Exception as e:
|
||||
if env_path.exists():
|
||||
self._install_comment(
|
||||
version, "An error occurred. Removing partial environment."
|
||||
)
|
||||
shutil.rmtree(env_path)
|
||||
|
||||
if env_path_saved.exists():
|
||||
self._install_comment(
|
||||
version, "Restoring previously saved environment."
|
||||
)
|
||||
shutil.move(env_path_saved, env_path)
|
||||
|
||||
raise e
|
||||
else:
|
||||
if env_path_saved.exists():
|
||||
shutil.rmtree(env_path_saved, ignore_errors=True)
|
||||
|
||||
def make_bin(self, version: str, env: VirtualEnvironment) -> None:
|
||||
self._install_comment(version, "Creating script")
|
||||
self.bin_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
script = "poetry.exe" if WINDOWS else "poetry"
|
||||
target_script = env.bin_path.joinpath(script)
|
||||
|
||||
if self.bin_dir.joinpath(script).exists():
|
||||
self.bin_dir.joinpath(script).unlink()
|
||||
|
||||
try:
|
||||
self.bin_dir.joinpath(script).symlink_to(target_script)
|
||||
except OSError:
|
||||
# This can happen if the user
|
||||
# does not have the correct permission on Windows
|
||||
shutil.copy(target_script, self.bin_dir.joinpath(script))
|
||||
|
||||
def install_poetry(self, version: str, env: VirtualEnvironment) -> None:
|
||||
self._install_comment(version, "Installing Poetry")
|
||||
|
||||
if self._git:
|
||||
specification = "git+" + version
|
||||
elif self._path:
|
||||
specification = version
|
||||
else:
|
||||
specification = f"poetry=={version}"
|
||||
|
||||
env.pip("install", specification)
|
||||
|
||||
def display_pre_message(self) -> None:
|
||||
kwargs = {
|
||||
"poetry": colorize("info", "Poetry"),
|
||||
"poetry_home_bin": colorize("comment", self.bin_dir),
|
||||
}
|
||||
self._write(PRE_MESSAGE.format(**kwargs))
|
||||
|
||||
def display_post_message(self, version: str) -> None:
|
||||
if WINDOWS:
|
||||
return self.display_post_message_windows(version)
|
||||
|
||||
if SHELL == "fish":
|
||||
return self.display_post_message_fish(version)
|
||||
|
||||
return self.display_post_message_unix(version)
|
||||
|
||||
def display_post_message_windows(self, version: str) -> None:
|
||||
path = self.get_windows_path_var()
|
||||
|
||||
message = POST_MESSAGE_NOT_IN_PATH
|
||||
if path and str(self.bin_dir) in path:
|
||||
message = POST_MESSAGE
|
||||
|
||||
self._write(
|
||||
message.format(
|
||||
poetry=colorize("info", "Poetry"),
|
||||
version=colorize("b", version),
|
||||
poetry_home_bin=colorize("comment", self.bin_dir),
|
||||
poetry_executable=colorize("b", self.bin_dir.joinpath("poetry")),
|
||||
configure_message=POST_MESSAGE_CONFIGURE_WINDOWS.format(
|
||||
poetry_home_bin=colorize("comment", self.bin_dir)
|
||||
),
|
||||
test_command=colorize("b", "poetry --version"),
|
||||
)
|
||||
)
|
||||
|
||||
def get_windows_path_var(self) -> Optional[str]:
|
||||
import winreg
|
||||
|
||||
with winreg.ConnectRegistry(
|
||||
None, winreg.HKEY_CURRENT_USER
|
||||
) as root, winreg.OpenKey(root, "Environment", 0, winreg.KEY_ALL_ACCESS) as key:
|
||||
path, _ = winreg.QueryValueEx(key, "PATH")
|
||||
|
||||
return path
|
||||
|
||||
def display_post_message_fish(self, version: str) -> None:
|
||||
fish_user_paths = subprocess.check_output(
|
||||
["fish", "-c", "echo $fish_user_paths"]
|
||||
).decode("utf-8")
|
||||
|
||||
message = POST_MESSAGE_NOT_IN_PATH
|
||||
if fish_user_paths and str(self.bin_dir) in fish_user_paths:
|
||||
message = POST_MESSAGE
|
||||
|
||||
self._write(
|
||||
message.format(
|
||||
poetry=colorize("info", "Poetry"),
|
||||
version=colorize("b", version),
|
||||
poetry_home_bin=colorize("comment", self.bin_dir),
|
||||
poetry_executable=colorize("b", self.bin_dir.joinpath("poetry")),
|
||||
configure_message=POST_MESSAGE_CONFIGURE_FISH.format(
|
||||
poetry_home_bin=colorize("comment", self.bin_dir)
|
||||
),
|
||||
test_command=colorize("b", "poetry --version"),
|
||||
)
|
||||
)
|
||||
|
||||
def display_post_message_unix(self, version: str) -> None:
|
||||
paths = os.getenv("PATH", "").split(":")
|
||||
|
||||
message = POST_MESSAGE_NOT_IN_PATH
|
||||
if paths and str(self.bin_dir) in paths:
|
||||
message = POST_MESSAGE
|
||||
|
||||
self._write(
|
||||
message.format(
|
||||
poetry=colorize("info", "Poetry"),
|
||||
version=colorize("b", version),
|
||||
poetry_home_bin=colorize("comment", self.bin_dir),
|
||||
poetry_executable=colorize("b", self.bin_dir.joinpath("poetry")),
|
||||
configure_message=POST_MESSAGE_CONFIGURE_UNIX.format(
|
||||
poetry_home_bin=colorize("comment", self.bin_dir)
|
||||
),
|
||||
test_command=colorize("b", "poetry --version"),
|
||||
)
|
||||
)
|
||||
|
||||
def ensure_directories(self) -> None:
|
||||
self.data_dir.mkdir(parents=True, exist_ok=True)
|
||||
self.bin_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
def get_version(self):
|
||||
current_version = None
|
||||
if self.version_file.exists():
|
||||
current_version = self.version_file.read_text().strip()
|
||||
|
||||
self._write(colorize("info", "Retrieving Poetry metadata"))
|
||||
|
||||
metadata = json.loads(self._get(self.METADATA_URL).decode())
|
||||
|
||||
def _compare_versions(x, y):
|
||||
mx = self.VERSION_REGEX.match(x)
|
||||
my = self.VERSION_REGEX.match(y)
|
||||
|
||||
vx = (*tuple(int(p) for p in mx.groups()[:3]), mx.group(5))
|
||||
vy = (*tuple(int(p) for p in my.groups()[:3]), my.group(5))
|
||||
|
||||
if vx < vy:
|
||||
return -1
|
||||
elif vx > vy:
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
self._write("")
|
||||
releases = sorted(
|
||||
metadata["releases"].keys(), key=cmp_to_key(_compare_versions)
|
||||
)
|
||||
|
||||
if self._version and self._version not in releases:
|
||||
msg = f"Version {self._version} does not exist."
|
||||
self._write(colorize("error", msg))
|
||||
|
||||
raise ValueError(msg)
|
||||
|
||||
version = self._version
|
||||
if not version:
|
||||
for release in reversed(releases):
|
||||
m = self.VERSION_REGEX.match(release)
|
||||
if m.group(5) and not self.allows_prereleases():
|
||||
continue
|
||||
|
||||
version = release
|
||||
|
||||
break
|
||||
|
||||
if current_version == version and not self._force:
|
||||
self._write(
|
||||
f'The latest version ({colorize("b", version)}) is already installed.'
|
||||
)
|
||||
|
||||
return None, current_version
|
||||
|
||||
return version, current_version
|
||||
|
||||
def _write(self, line) -> None:
|
||||
sys.stdout.write(line + "\n")
|
||||
|
||||
def _overwrite(self, line) -> None:
|
||||
if not is_decorated():
|
||||
return self._write(line)
|
||||
|
||||
self._cursor.move_up()
|
||||
self._cursor.clear_line()
|
||||
self._write(line)
|
||||
|
||||
def _get(self, url):
|
||||
request = Request(url, headers={"User-Agent": "Python Poetry"})
|
||||
|
||||
with closing(urlopen(request)) as r:
|
||||
return r.read()
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Installs the latest (or given) version of poetry"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-p",
|
||||
"--preview",
|
||||
help="install preview version",
|
||||
dest="preview",
|
||||
action="store_true",
|
||||
default=False,
|
||||
)
|
||||
parser.add_argument("--version", help="install named version", dest="version")
|
||||
parser.add_argument(
|
||||
"-f",
|
||||
"--force",
|
||||
help="install on top of existing version",
|
||||
dest="force",
|
||||
action="store_true",
|
||||
default=False,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-y",
|
||||
"--yes",
|
||||
help="accept all prompts",
|
||||
dest="accept_all",
|
||||
action="store_true",
|
||||
default=False,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--uninstall",
|
||||
help="uninstall poetry",
|
||||
dest="uninstall",
|
||||
action="store_true",
|
||||
default=False,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--path",
|
||||
dest="path",
|
||||
action="store",
|
||||
help=(
|
||||
"Install from a given path (file or directory) instead of "
|
||||
"fetching the latest version of Poetry available online."
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--git",
|
||||
dest="git",
|
||||
action="store",
|
||||
help=(
|
||||
"Install from a git repository instead of fetching the latest version "
|
||||
"of Poetry available online."
|
||||
),
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
installer = Installer(
|
||||
version=args.version or os.getenv("POETRY_VERSION"),
|
||||
preview=args.preview or string_to_bool(os.getenv("POETRY_PREVIEW", "0")),
|
||||
force=args.force,
|
||||
accept_all=args.accept_all
|
||||
or string_to_bool(os.getenv("POETRY_ACCEPT", "0"))
|
||||
or not is_interactive(),
|
||||
path=args.path,
|
||||
git=args.git,
|
||||
)
|
||||
|
||||
if args.uninstall or string_to_bool(os.getenv("POETRY_UNINSTALL", "0")):
|
||||
return installer.uninstall()
|
||||
|
||||
try:
|
||||
return installer.run()
|
||||
except PoetryInstallationError as e:
|
||||
installer._write(colorize("error", "Poetry installation failed."))
|
||||
|
||||
if e.log is not None:
|
||||
import traceback
|
||||
|
||||
_, path = tempfile.mkstemp(
|
||||
suffix=".log",
|
||||
prefix="poetry-installer-error-",
|
||||
dir=str(Path.cwd()),
|
||||
text=True,
|
||||
)
|
||||
installer._write(colorize("error", f"See {path} for error logs."))
|
||||
tb = "".join(traceback.format_tb(e.__traceback__))
|
||||
text = f"{e.log}\nTraceback:\n\n{tb}"
|
||||
Path(path).write_text(text)
|
||||
|
||||
return e.return_code
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -149,5 +149,7 @@
|
||||
<orderEntry type="module" module-name="intellij.python.community.impl.huggingFace" scope="RUNTIME" />
|
||||
<orderEntry type="module" module-name="intellij.python.syntax" />
|
||||
<orderEntry type="module" module-name="intellij.platform.ui.jcef" />
|
||||
<orderEntry type="module" module-name="intellij.libraries.ktor.client" />
|
||||
<orderEntry type="module" module-name="intellij.libraries.ktor.client.cio" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -26,6 +26,8 @@ The Python plug-in provides smart editing for Python scripts. The feature set of
|
||||
<plugin id="com.intellij.modules.python-core-capable"/>
|
||||
<plugin id="com.intellij.modules.json"/>
|
||||
<plugin id="org.toml.lang"/>
|
||||
<module name="intellij.libraries.ktor.client"/>
|
||||
<module name="intellij.libraries.ktor.client.cio"/>
|
||||
</dependencies>
|
||||
|
||||
<!-- Python bundled modules for content -->
|
||||
|
||||
@@ -316,6 +316,7 @@ python.sdk.popup.interpreter.settings=Interpreter Settings\u2026
|
||||
python.sdk.popup.add.interpreter=Add Interpreter\u2026
|
||||
python.sdk.switch.to=Switch to {0}
|
||||
python.sdk.installing=Installing {0}
|
||||
python.sdk.downloading.package.progress.title=Downloading package from {0}
|
||||
python.sdk.select.conda.path.title=Select Path to Conda Executable
|
||||
python.sdk.conda.problem.running=Problem running conda
|
||||
python.sdk.conda.problem.env.empty.invalid=Environment name is empty or invalid
|
||||
@@ -367,7 +368,6 @@ python.sdk.poetry.associated.project=Associated project:
|
||||
python.sdk.poetry.associated.module=Associated module:
|
||||
python.sdk.poetry.execution.exception.no.project.message=Cannot find the project associated with this Poetry environment
|
||||
python.sdk.poetry.execution.exception.no.poetry.message=Cannot find Poetry
|
||||
python.sdk.poetry.execution.exception.error.running.poetry.message=Error Running Poetry
|
||||
python.sdk.poetry.quickfix.fix.pipenv.name=Fix Poetry interpreter
|
||||
python.sdk.poetry.quickfix.use.pipenv.name=Use Poetry interpreter
|
||||
python.sdk.poetry.pip.file.lock.not.found=poetry.lock is not found
|
||||
@@ -494,9 +494,6 @@ sdk.create.type.project.venv=Project venv
|
||||
sdk.create.type.base.conda=Base conda
|
||||
sdk.create.type.custom=Custom environment
|
||||
sdk.create.python.version=Python version:
|
||||
sdk.create.conda.executable.path=Path to conda:
|
||||
sdk.create.conda.missing.text=No conda executable found.
|
||||
sdk.create.conda.install.fix=Install Miniconda
|
||||
sdk.create.simple.venv.hint=Python virtual environment will be created in the project root: {0}.venv
|
||||
sdk.create.simple.conda.hint=To create a new conda environment or choose an existing one, proceed with Custom environment
|
||||
sdk.create.custom.develop.on=Develop on:
|
||||
@@ -506,6 +503,7 @@ sdk.create.custom.select.existing=Select existing
|
||||
sdk.create.custom.type=Type:
|
||||
sdk.create.custom.base.python=Base python:
|
||||
sdk.create.custom.location=Location:
|
||||
sdk.create.custom.venv.missing.text=No {0} executable found.
|
||||
sdk.create.custom.inherit.packages=Inherit packages from base interpreter
|
||||
sdk.create.custom.make.available=Make available to all projects
|
||||
sdk.create.custom.python.path=Python path:
|
||||
@@ -516,10 +514,7 @@ sdk.create.custom.conda.env.name=Name:
|
||||
sdk.create.custom.conda.create.progress=Creating conda interpreter
|
||||
sdk.create.custom.conda.select.progress=Selecting conda interpreter
|
||||
sdk.create.custom.conda.refresh.envs=Reload environments
|
||||
sdk.create.custom.pipenv.path=Path to pipenv:
|
||||
sdk.create.custom.pipenv.missing.text=No pipenv executable found.
|
||||
sdk.create.custom.poetry.path=Path to poetry:
|
||||
sdk.create.custom.poetry.missing.text=No poetry executable non found.
|
||||
sdk.create.custom.venv.executable.path=Path to {0}:
|
||||
sdk.create.custom.select.executable.link=Select path
|
||||
sdk.create.custom.venv.environment.exists=Environment "{0}" already exists in the specified folder
|
||||
sdk.create.custom.venv.folder.not.empty=The specified folder already exists
|
||||
@@ -529,7 +524,8 @@ sdk.create.not.executable.empty.error=Specify path to executable
|
||||
sdk.create.not.executable.does.not.exist.error=Executable does not exist
|
||||
sdk.create.executable.directory.error=Path can't be a directory
|
||||
sdk.create.tooltip.browse=Browse\u2026
|
||||
|
||||
sdk.create.custom.venv.install.fix.title=Install {0} {1}
|
||||
sdk.create.custom.venv.run.error.message= Error Running {0}
|
||||
|
||||
sdk.create.targets.local=Local Machine
|
||||
sdk.create.custom.virtualenv=Virtualenv
|
||||
|
||||
93
python/src/com/jetbrains/python/sdk/PySdkCommandRunner.kt
Normal file
93
python/src/com/jetbrains/python/sdk/PySdkCommandRunner.kt
Normal file
@@ -0,0 +1,93 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.python.sdk
|
||||
|
||||
import com.intellij.execution.RunCanceledByUserException
|
||||
import com.intellij.execution.configurations.GeneralCommandLine
|
||||
import com.intellij.execution.process.CapturingProcessHandler
|
||||
import com.intellij.execution.process.ProcessOutput
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.openapi.util.NlsContexts
|
||||
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
|
||||
import com.jetbrains.python.packaging.IndicatedProcessOutputListener
|
||||
import com.jetbrains.python.packaging.PyExecutionException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.absolutePathString
|
||||
import kotlin.io.path.pathString
|
||||
|
||||
internal object Logger {
|
||||
val LOG = logger<Logger>()
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for CoroutineScope in com.jetbrains.python.sdk
|
||||
*/
|
||||
@Service(Service.Level.PROJECT)
|
||||
internal class PythonSdkRunCommandService(val cs: CoroutineScope)
|
||||
|
||||
/**
|
||||
* Runs a command line operation in a background thread.
|
||||
*
|
||||
* @param [commandLine] The command line to execute.
|
||||
* @return A [Result] object containing the output of the command execution.
|
||||
*/
|
||||
@RequiresBackgroundThread
|
||||
internal fun runCommandLine(commandLine: GeneralCommandLine): Result<ProcessOutput> {
|
||||
Logger.LOG.info("Running command: ${commandLine.commandLineString}")
|
||||
val commandOutput = with(CapturingProcessHandler(commandLine)) {
|
||||
runProcess()
|
||||
}
|
||||
|
||||
return processOutput(
|
||||
commandOutput,
|
||||
commandLine.commandLineString,
|
||||
emptyList()
|
||||
)
|
||||
}
|
||||
|
||||
fun runCommand(executable: Path, projectPath: Path?, @NlsContexts.DialogMessage errorMessage: String, vararg args: String): String {
|
||||
val command = listOf(executable.absolutePathString()) + args
|
||||
val commandLine = GeneralCommandLine(command).withWorkingDirectory(projectPath)
|
||||
val handler = CapturingProcessHandler(commandLine)
|
||||
val indicator = ProgressManager.getInstance().progressIndicator
|
||||
val result = with(handler) {
|
||||
when {
|
||||
indicator != null -> {
|
||||
addProcessListener(IndicatedProcessOutputListener(indicator))
|
||||
runProcessWithProgressIndicator(indicator)
|
||||
}
|
||||
else ->
|
||||
runProcess()
|
||||
}
|
||||
}
|
||||
|
||||
return processOutput(result, executable.pathString, args.asList(), errorMessage).getOrThrow().stdout.trim()
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the output of a command execution.
|
||||
*
|
||||
* @param[output] the output of the executed command.
|
||||
* @param[commandString] the command string that was executed.
|
||||
* @param[args] the arguments passed to the command.
|
||||
* @param[errorMessage] the error message to be used if the command fails.
|
||||
* @return A [Result] object containing the processed output.
|
||||
*/
|
||||
internal fun processOutput(
|
||||
output: ProcessOutput,
|
||||
commandString: String,
|
||||
args: List<String>,
|
||||
@NlsContexts.DialogMessage errorMessage: String = "",
|
||||
): Result<ProcessOutput> {
|
||||
return with(output) {
|
||||
when {
|
||||
isCancelled ->
|
||||
Result.failure(RunCanceledByUserException())
|
||||
exitCode != 0 ->
|
||||
Result.failure(PyExecutionException(errorMessage, commandString, args, stdout, stderr, exitCode, emptyList()))
|
||||
else -> Result.success(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,15 +16,11 @@
|
||||
package com.jetbrains.python.sdk
|
||||
|
||||
import com.intellij.execution.ExecutionException
|
||||
import com.intellij.execution.RunCanceledByUserException
|
||||
import com.intellij.execution.configurations.GeneralCommandLine
|
||||
import com.intellij.execution.process.CapturingProcessHandler
|
||||
import com.intellij.execution.target.*
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.application.PathManager
|
||||
import com.intellij.openapi.application.WriteAction
|
||||
import com.intellij.openapi.application.runInEdt
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.diagnostic.getOrLogException
|
||||
import com.intellij.openapi.diagnostic.thisLogger
|
||||
import com.intellij.openapi.module.Module
|
||||
@@ -50,8 +46,6 @@ import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.util.PathUtil
|
||||
import com.intellij.webcore.packaging.PackagesNotificationPanel
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.packaging.IndicatedProcessOutputListener
|
||||
import com.jetbrains.python.packaging.PyExecutionException
|
||||
import com.jetbrains.python.packaging.ui.PyPackageManagementService
|
||||
import com.jetbrains.python.psi.LanguageLevel
|
||||
import com.jetbrains.python.remote.PyRemoteSdkAdditionalData
|
||||
@@ -64,14 +58,12 @@ import com.jetbrains.python.sdk.flavors.VirtualEnvSdkFlavor
|
||||
import com.jetbrains.python.sdk.flavors.conda.CondaEnvSdkFlavor
|
||||
import com.jetbrains.python.target.PyTargetAwareAdditionalData
|
||||
import com.jetbrains.python.ui.PyUiUtil
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import kotlin.io.path.absolutePathString
|
||||
import kotlin.io.path.div
|
||||
import kotlin.io.path.pathString
|
||||
|
||||
@@ -135,18 +127,22 @@ fun detectSystemWideSdks(
|
||||
{ it.homePath }).reversed())
|
||||
}
|
||||
|
||||
private fun PythonSdkFlavor<*>.detectSdks(module: Module?,
|
||||
context: UserDataHolder,
|
||||
targetModuleSitsOn: TargetConfigurationWithLocalFsAccess?,
|
||||
existingPaths: HashSet<TargetAndPath>): List<PyDetectedSdk> =
|
||||
private fun PythonSdkFlavor<*>.detectSdks(
|
||||
module: Module?,
|
||||
context: UserDataHolder,
|
||||
targetModuleSitsOn: TargetConfigurationWithLocalFsAccess?,
|
||||
existingPaths: HashSet<TargetAndPath>,
|
||||
): List<PyDetectedSdk> =
|
||||
detectSdkPaths(module, context, targetModuleSitsOn, existingPaths)
|
||||
.map { createDetectedSdk(it, targetModuleSitsOn?.asTargetConfig, this) }
|
||||
|
||||
|
||||
internal fun PythonSdkFlavor<*>.detectSdkPaths(module: Module?,
|
||||
context: UserDataHolder,
|
||||
targetModuleSitsOn: TargetConfigurationWithLocalFsAccess?,
|
||||
existingPaths: HashSet<TargetAndPath>): List<String> =
|
||||
internal fun PythonSdkFlavor<*>.detectSdkPaths(
|
||||
module: Module?,
|
||||
context: UserDataHolder,
|
||||
targetModuleSitsOn: TargetConfigurationWithLocalFsAccess?,
|
||||
existingPaths: HashSet<TargetAndPath>,
|
||||
): List<String> =
|
||||
suggestLocalHomePaths(module, context)
|
||||
.mapNotNull {
|
||||
// If a module sits on target, this target maps its path.
|
||||
@@ -565,44 +561,10 @@ val Sdk.sdkSeemsValid: Boolean
|
||||
return pythonSdkAdditionalData.flavorAndData.sdkSeemsValid(this, targetEnvConfiguration)
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for CoroutineScope in com.jetbrains.python.sdk
|
||||
*/
|
||||
@Service(Service.Level.PROJECT)
|
||||
class PythonSdkRunCommandService(val cs: CoroutineScope)
|
||||
|
||||
fun runCommand(executable: Path, projectPath: Path?, @NlsContexts.DialogMessage errorMessage: String, vararg args: String): String {
|
||||
val command = listOf(executable.absolutePathString()) + args
|
||||
val commandLine = GeneralCommandLine(command).withWorkingDirectory(projectPath)
|
||||
val handler = CapturingProcessHandler(commandLine)
|
||||
val indicator = ProgressManager.getInstance().progressIndicator
|
||||
val result = with(handler) {
|
||||
when {
|
||||
indicator != null -> {
|
||||
addProcessListener(IndicatedProcessOutputListener(indicator))
|
||||
runProcessWithProgressIndicator(indicator)
|
||||
}
|
||||
else ->
|
||||
runProcess()
|
||||
}
|
||||
}
|
||||
return with(result) {
|
||||
when {
|
||||
isCancelled ->
|
||||
throw RunCanceledByUserException()
|
||||
exitCode != 0 ->
|
||||
throw PyExecutionException(errorMessage, executable.pathString,
|
||||
args.asList(),
|
||||
stdout, stderr, exitCode, emptyList())
|
||||
else -> stdout.trim()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <reified T : PythonSdkAdditionalData> setCorrectTypeSdk(sdk: Sdk, additionalDataClass: Class<T>, value: Boolean) {
|
||||
val oldData = sdk.sdkAdditionalData
|
||||
val newData = if (value) {
|
||||
when(oldData) {
|
||||
when (oldData) {
|
||||
is PythonSdkAdditionalData -> additionalDataClass.getDeclaredConstructors().first { it.parameterCount == 1 }.newInstance(oldData) as T
|
||||
else -> additionalDataClass.getDeclaredConstructor().newInstance()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.python.sdk
|
||||
|
||||
import com.intellij.execution.RunCanceledByUserException
|
||||
import com.intellij.execution.configurations.GeneralCommandLine
|
||||
import com.intellij.execution.process.ProcessOutput
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.util.SystemInfo
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
|
||||
import com.jetbrains.python.packaging.PyExecutionException
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.engine.cio.CIO
|
||||
import io.ktor.client.plugins.HttpTimeout
|
||||
import io.ktor.client.request.get
|
||||
import io.ktor.client.statement.bodyAsChannel
|
||||
import io.ktor.util.cio.writeChannel
|
||||
import io.ktor.utils.io.copyAndClose
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jetbrains.annotations.ApiStatus.Internal
|
||||
import java.net.URL
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.absolutePathString
|
||||
|
||||
/**
|
||||
* Returns the string representation of the Python executable ("py" on Windows or "python" on Unix-like OS) based on the current system.
|
||||
*
|
||||
* @return the string representation of the Python executable
|
||||
*/
|
||||
@Internal
|
||||
fun getPythonExecutableString() = if (SystemInfo.isWindows) "py" else "python"
|
||||
|
||||
|
||||
@Service(Service.Level.APP)
|
||||
internal class PackageInstallationFilesService {
|
||||
val ktorClient = HttpClient(CIO) {
|
||||
install(HttpTimeout)
|
||||
}
|
||||
val urlToFilePathMap = mutableMapOf<URL, Path>()
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs a package with Python using the given URL and Python executable.
|
||||
*
|
||||
* @param [url] The [URL] from which to download the package.
|
||||
* @param pythonExecutable The path to the Python executable (could be "py" or "python").
|
||||
* @return A [Result] object that represents the [ProcessOutput] of the installation command.
|
||||
*/
|
||||
@RequiresBackgroundThread
|
||||
internal suspend fun installPackageWithPython(url: URL, pythonExecutable: String): Result<ProcessOutput> {
|
||||
val installationFile = downloadFile(url).getOrThrow()
|
||||
val command = GeneralCommandLine(pythonExecutable, installationFile.absolutePathString())
|
||||
return runCommandLine(command)
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads a file from the specified URL.
|
||||
*
|
||||
* @param[url] The [URL] from which to download the file.
|
||||
* @return A [Result] object that represents the [Path] to the downloaded file.
|
||||
*/
|
||||
internal suspend fun downloadFile(url: URL): Result<Path> {
|
||||
val installationService = service<PackageInstallationFilesService>()
|
||||
installationService.urlToFilePathMap[url]?.let { return Result.success(it) }
|
||||
return withContext(Dispatchers.IO) {
|
||||
val installationFile = FileUtil.createTempFile("_installation_file.py", null)
|
||||
installationService.ktorClient.get(url).bodyAsChannel().copyAndClose(installationFile.writeChannel())
|
||||
installationService.urlToFilePathMap[url] = installationFile.toPath()
|
||||
|
||||
Result.success(installationFile.toPath())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a package is installed by running the specified command(s) with the "--version"
|
||||
* argument and checking the success of the command.
|
||||
*
|
||||
* @param [commands] The commands to execute. These commands should include the package and any required arguments.
|
||||
* @return true if the package is installed, false otherwise
|
||||
*/
|
||||
@Internal
|
||||
@RequiresBackgroundThread
|
||||
fun isPackageInstalled(vararg commands: String): Boolean {
|
||||
val command = GeneralCommandLine(*commands, "--version")
|
||||
return runCommandLine(command).isSuccess
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs an executable via pip.
|
||||
*
|
||||
* @param [executableName] The name of the executable to install.
|
||||
* @param [pythonExecutable] The path to the Python executable (could be "py" or "python").
|
||||
* @param [isUserSitePackages] Whether to install the executable in the user's site packages directory. Defaults to true.
|
||||
*/
|
||||
@Internal
|
||||
@RequiresBackgroundThread
|
||||
fun installExecutableViaPip(
|
||||
executableName: String,
|
||||
pythonExecutable: String,
|
||||
isUserSitePackages: Boolean = true,
|
||||
) {
|
||||
val commandList = mutableListOf(pythonExecutable, "-m", "pip", "install", executableName)
|
||||
if (isUserSitePackages) {
|
||||
commandList.add("--user")
|
||||
}
|
||||
|
||||
runCommandLine(GeneralCommandLine(commandList)).getOrThrow()
|
||||
}
|
||||
|
||||
@RequiresBackgroundThread
|
||||
internal suspend fun installPipIfNeeded(pythonExecutable: String) {
|
||||
if (!isPackageInstalled(pythonExecutable, "-m", "pip") && !isPackageInstalled("pip")) {
|
||||
installPackageWithPython(URL("https://bootstrap.pypa.io/get-pip.py"), pythonExecutable).getOrThrow()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs an executable via a Python script.
|
||||
*
|
||||
* @param [scriptPath] The [Path] to the Python script used for installation.
|
||||
* @param [pythonExecutable] The path to the Python executable (could be "py" or "python").
|
||||
*
|
||||
* @throws [RunCanceledByUserException] if the user cancels the command execution.
|
||||
* @throws [PyExecutionException] if the command execution fails.
|
||||
*/
|
||||
@RequiresBackgroundThread
|
||||
internal fun installExecutableViaPythonScript(scriptPath: Path, pythonExecutable: String) =
|
||||
runCommandLine(GeneralCommandLine(pythonExecutable, scriptPath.absolutePathString())).getOrThrow()
|
||||
@@ -28,8 +28,8 @@ class CondaExistingEnvironmentSelector(model: PythonAddInterpreterModel) : Pytho
|
||||
with(panel) {
|
||||
executableSelector(state.condaExecutable,
|
||||
validationRequestor,
|
||||
message("sdk.create.conda.executable.path"),
|
||||
message("sdk.create.conda.missing.text"),
|
||||
message("sdk.create.custom.venv.executable.path", "conda"),
|
||||
message("sdk.create.custom.venv.missing.text", "conda"),
|
||||
createInstallCondaFix(model))
|
||||
.displayLoaderWhen(model.condaEnvironmentsLoading, scope = model.scope, uiContext = model.uiContext)
|
||||
|
||||
|
||||
@@ -38,8 +38,8 @@ class CondaNewEnvironmentCreator(model: PythonMutableTargetAddInterpreterModel)
|
||||
|
||||
executableSelector(model.state.condaExecutable,
|
||||
validationRequestor,
|
||||
message("sdk.create.conda.executable.path"),
|
||||
message("sdk.create.conda.missing.text"),
|
||||
message("sdk.create.custom.venv.executable.path", "conda"),
|
||||
message("sdk.create.custom.venv.missing.text", "conda"),
|
||||
createInstallCondaFix(model))
|
||||
.displayLoaderWhen(model.condaEnvironmentsLoading, scope = model.scope, uiContext = model.uiContext)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.python.sdk.add.v2
|
||||
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.observable.properties.ObservableMutableProperty
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil
|
||||
import com.intellij.openapi.ui.validation.DialogValidationRequestor
|
||||
import com.intellij.platform.ide.progress.ModalTaskOwner
|
||||
import com.intellij.platform.ide.progress.runWithModalProgressBlocking
|
||||
import com.intellij.ui.components.ActionLink
|
||||
import com.intellij.ui.dsl.builder.Align
|
||||
import com.intellij.ui.dsl.builder.Panel
|
||||
import com.intellij.util.concurrency.annotations.RequiresEdt
|
||||
import com.jetbrains.python.PyBundle.message
|
||||
import com.jetbrains.python.newProject.collector.InterpreterStatisticsInfo
|
||||
import com.jetbrains.python.sdk.*
|
||||
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor
|
||||
import com.jetbrains.python.sdk.installPipIfNeeded
|
||||
import com.jetbrains.python.statistics.InterpreterCreationMode
|
||||
import com.jetbrains.python.statistics.InterpreterType
|
||||
import org.jetbrains.annotations.ApiStatus.Internal
|
||||
import java.nio.file.Path
|
||||
|
||||
@Internal
|
||||
abstract class CustomNewEnvironmentCreator(private val name: String, model: PythonMutableTargetAddInterpreterModel) : PythonNewEnvironmentCreator(model) {
|
||||
protected lateinit var basePythonComboBox: PythonInterpreterComboBox
|
||||
|
||||
override fun buildOptions(panel: Panel, validationRequestor: DialogValidationRequestor) {
|
||||
with(panel) {
|
||||
row(message("sdk.create.custom.base.python")) {
|
||||
basePythonComboBox = pythonInterpreterComboBox(model.state.baseInterpreter,
|
||||
model,
|
||||
model::addInterpreter,
|
||||
model.interpreterLoading)
|
||||
.align(Align.FILL)
|
||||
.component
|
||||
}
|
||||
|
||||
executableSelector(executable,
|
||||
validationRequestor,
|
||||
message("sdk.create.custom.venv.executable.path", name),
|
||||
message("sdk.create.custom.venv.missing.text", name),
|
||||
createInstallFix()).component
|
||||
}
|
||||
}
|
||||
|
||||
override fun onShown() {
|
||||
basePythonComboBox.setItems(model.baseInterpreters)
|
||||
}
|
||||
|
||||
override fun getOrCreateSdk(): Sdk {
|
||||
savePathToExecutableToProperties()
|
||||
|
||||
// todo think about better error handling
|
||||
val selectedBasePython = model.state.baseInterpreter.get()!!
|
||||
val homePath = model.installPythonIfNeeded(selectedBasePython)
|
||||
|
||||
val newSdk = setupEnvSdk(null,
|
||||
null,
|
||||
model.baseSdks,
|
||||
model.projectPath.value,
|
||||
homePath,
|
||||
false)!!
|
||||
SdkConfigurationUtil.addSdk(newSdk)
|
||||
return newSdk
|
||||
}
|
||||
|
||||
override fun createStatisticsInfo(target: PythonInterpreterCreationTargets): InterpreterStatisticsInfo =
|
||||
InterpreterStatisticsInfo(interpreterType,
|
||||
target.toStatisticsField(),
|
||||
false,
|
||||
false,
|
||||
false, //presenter.projectLocationContext is WslContext,
|
||||
false, // todo fix for wsl
|
||||
InterpreterCreationMode.CUSTOM)
|
||||
|
||||
/**
|
||||
* Creates an installation fix for executable (poetry, pipenv).
|
||||
*
|
||||
* 1. Checks does a `pythonExecutable` have pip.
|
||||
* 2. If no, checks is pip is installed globally.
|
||||
* 3. If no, downloads and installs pip from "https://bootstrap.pypa.io/get-pip.py"
|
||||
* 4. Runs (pythonExecutable -m) pip install `package_name` --user
|
||||
* 5. Reruns `detectExecutable`
|
||||
*/
|
||||
private fun createInstallFix(): ActionLink {
|
||||
return ActionLink(message("sdk.create.custom.venv.install.fix.title", name, "via pip")) {
|
||||
PythonSdkFlavor.clearExecutablesCache()
|
||||
installExecutable()
|
||||
detectExecutable()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs the necessary executable in the Python environment.
|
||||
*
|
||||
* Initiates a blocking modal progress task to:
|
||||
* 1. Ensure pip is installed.
|
||||
* 2. Install the executable (specified by `name`) using either a custom installation script or via pip.
|
||||
*/
|
||||
@RequiresEdt
|
||||
private fun installExecutable() {
|
||||
val pythonExecutable = model.state.baseInterpreter.get()?.homePath ?: getPythonExecutableString()
|
||||
runWithModalProgressBlocking(ModalTaskOwner.guess(), message("sdk.create.custom.venv.install.fix.title", name, "via pip")) {
|
||||
installPipIfNeeded(pythonExecutable)
|
||||
|
||||
if (installationScript != null) {
|
||||
installExecutableViaPythonScript(installationScript!!, pythonExecutable)
|
||||
}
|
||||
else {
|
||||
installExecutableViaPip(name, pythonExecutable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal abstract val interpreterType: InterpreterType
|
||||
|
||||
internal abstract val executable: ObservableMutableProperty<String>
|
||||
|
||||
/**
|
||||
* The `installationScript` specifies a custom script for installing an executable in the Python environment.
|
||||
*
|
||||
* If this property is not null, the provided script will be used for installation instead of the default pip installation.
|
||||
*/
|
||||
internal abstract val installationScript: Path?
|
||||
|
||||
internal abstract fun savePathToExecutableToProperties()
|
||||
|
||||
internal abstract fun setupEnvSdk(project: Project?, module: Module?, baseSdks: List<Sdk>, projectPath: String, homePath: String?, installPackages: Boolean): Sdk?
|
||||
|
||||
internal abstract fun detectExecutable()
|
||||
}
|
||||
@@ -2,89 +2,30 @@
|
||||
package com.jetbrains.python.sdk.add.v2
|
||||
|
||||
import com.intellij.ide.util.PropertiesComponent
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.observable.properties.ObservableMutableProperty
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil
|
||||
import com.intellij.openapi.ui.validation.DialogValidationRequestor
|
||||
import com.intellij.ui.dsl.builder.Align
|
||||
import com.intellij.ui.dsl.builder.Panel
|
||||
import com.intellij.util.text.nullize
|
||||
import com.jetbrains.python.PyBundle.message
|
||||
import com.jetbrains.python.newProject.collector.InterpreterStatisticsInfo
|
||||
import com.jetbrains.python.sdk.pipenv.pipEnvPath
|
||||
import com.jetbrains.python.sdk.pipenv.setupPipEnvSdkUnderProgress
|
||||
import com.jetbrains.python.statistics.InterpreterCreationMode
|
||||
import com.jetbrains.python.statistics.InterpreterType
|
||||
import java.nio.file.Path
|
||||
|
||||
class PipEnvNewEnvironmentCreator(model: PythonMutableTargetAddInterpreterModel) : PythonNewEnvironmentCreator(model) {
|
||||
|
||||
private lateinit var basePythonComboBox: PythonInterpreterComboBox
|
||||
|
||||
override fun buildOptions(panel: Panel, validationRequestor: DialogValidationRequestor) {
|
||||
with(panel) {
|
||||
row(message("sdk.create.custom.base.python")) {
|
||||
basePythonComboBox = pythonInterpreterComboBox(model.state.baseInterpreter,
|
||||
model,
|
||||
model::addInterpreter,
|
||||
model.interpreterLoading)
|
||||
.align(Align.FILL)
|
||||
.component
|
||||
}
|
||||
|
||||
executableSelector(model.state.pipenvExecutable,
|
||||
validationRequestor,
|
||||
message("sdk.create.custom.pipenv.path"),
|
||||
message("sdk.create.custom.pipenv.missing.text")).component
|
||||
}
|
||||
class PipEnvNewEnvironmentCreator(model: PythonMutableTargetAddInterpreterModel) : CustomNewEnvironmentCreator("pipenv", model) {
|
||||
override val interpreterType: InterpreterType = InterpreterType.PIPENV
|
||||
override val executable: ObservableMutableProperty<String> = model.state.pipenvExecutable
|
||||
override val installationScript: Path? = null
|
||||
|
||||
override fun savePathToExecutableToProperties() {
|
||||
PropertiesComponent.getInstance().pipEnvPath = executable.get().nullize()
|
||||
}
|
||||
|
||||
override fun setupEnvSdk(project: Project?, module: Module?, baseSdks: List<Sdk>, projectPath: String, homePath: String?, installPackages: Boolean): Sdk? =
|
||||
setupPipEnvSdkUnderProgress(project, module, baseSdks, projectPath, homePath, installPackages)
|
||||
|
||||
override fun onShown() {
|
||||
basePythonComboBox.setItems(model.baseInterpreters)
|
||||
|
||||
//val savedPath = PropertiesComponent.getInstance().pipEnvPath
|
||||
//if (savedPath != null) {
|
||||
// model.state.pipenvExecutable.set(savedPath)
|
||||
//}
|
||||
//else {
|
||||
// val modalityState = ModalityState.current().asContextElement()
|
||||
// model.scope.launch(Dispatchers.IO) {
|
||||
// val detectedExecutable = detectPipEnvExecutable()
|
||||
// withContext(Dispatchers.EDT + modalityState) {
|
||||
// detectedExecutable?.let { model.state.pipenvExecutable.set(it.path) }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
||||
override fun getOrCreateSdk(): Sdk {
|
||||
if (model is PythonLocalAddInterpreterModel) {
|
||||
PropertiesComponent.getInstance().pipEnvPath = model.state.pipenvExecutable.get().nullize()
|
||||
}
|
||||
|
||||
// todo think about better error handling
|
||||
val selectedBasePython = model.state.baseInterpreter.get()!!
|
||||
val homePath = model.installPythonIfNeeded(selectedBasePython)
|
||||
|
||||
val newSdk = setupPipEnvSdkUnderProgress(null, null,
|
||||
model.baseSdks,
|
||||
model.projectPath.value,
|
||||
homePath, false)!!
|
||||
SdkConfigurationUtil.addSdk(newSdk)
|
||||
return newSdk
|
||||
}
|
||||
|
||||
|
||||
override fun createStatisticsInfo(target: PythonInterpreterCreationTargets): InterpreterStatisticsInfo {
|
||||
//val statisticsTarget = if (presenter.projectLocationContext is WslContext) InterpreterTarget.TARGET_WSL else target.toStatisticsField()
|
||||
val statisticsTarget = target.toStatisticsField() // todo fix for wsl
|
||||
return InterpreterStatisticsInfo(InterpreterType.PIPENV,
|
||||
statisticsTarget,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
//presenter.projectLocationContext is WslContext,
|
||||
false, // todo fix for wsl
|
||||
InterpreterCreationMode.CUSTOM)
|
||||
override fun detectExecutable() {
|
||||
model.detectPipEnvExecutable()
|
||||
}
|
||||
}
|
||||
@@ -2,44 +2,24 @@
|
||||
package com.jetbrains.python.sdk.add.v2
|
||||
|
||||
import com.intellij.ide.util.PropertiesComponent
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.observable.properties.ObservableMutableProperty
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil
|
||||
import com.intellij.openapi.ui.validation.DialogValidationRequestor
|
||||
import com.intellij.ui.dsl.builder.Align
|
||||
import com.intellij.ui.dsl.builder.Panel
|
||||
import com.intellij.util.text.nullize
|
||||
import com.jetbrains.python.PyBundle.message
|
||||
import com.jetbrains.python.newProject.collector.InterpreterStatisticsInfo
|
||||
import com.jetbrains.python.sdk.ModuleOrProject
|
||||
import com.jetbrains.python.sdk.baseDir
|
||||
import com.jetbrains.python.sdk.poetry.PyProjectTomlPythonVersionsService
|
||||
import com.jetbrains.python.PythonHelpersLocator
|
||||
import com.jetbrains.python.sdk.poetry.poetryPath
|
||||
import com.jetbrains.python.sdk.poetry.setupPoetrySdkUnderProgress
|
||||
import com.jetbrains.python.statistics.InterpreterCreationMode
|
||||
import com.jetbrains.python.statistics.InterpreterType
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
class PoetryNewEnvironmentCreator(model: PythonMutableTargetAddInterpreterModel, private val moduleOrProject: ModuleOrProject?) : PythonNewEnvironmentCreator(model) {
|
||||
|
||||
private lateinit var basePythonComboBox: PythonInterpreterComboBox
|
||||
|
||||
override fun buildOptions(panel: Panel, validationRequestor: DialogValidationRequestor) {
|
||||
with(panel) {
|
||||
row(message("sdk.create.custom.base.python")) {
|
||||
basePythonComboBox = pythonInterpreterComboBox(model.state.baseInterpreter,
|
||||
model,
|
||||
model::addInterpreter,
|
||||
model.interpreterLoading)
|
||||
.align(Align.FILL)
|
||||
.component
|
||||
}
|
||||
|
||||
executableSelector(model.state.poetryExecutable,
|
||||
validationRequestor,
|
||||
message("sdk.create.custom.poetry.path"),
|
||||
message("sdk.create.custom.poetry.missing.text")).component
|
||||
}
|
||||
}
|
||||
class PoetryNewEnvironmentCreator(model: PythonMutableTargetAddInterpreterModel, private val moduleOrProject: ModuleOrProject?) : CustomNewEnvironmentCreator("poetry", model) {
|
||||
override val interpreterType: InterpreterType = InterpreterType.POETRY
|
||||
override val executable: ObservableMutableProperty<String> = model.state.poetryExecutable
|
||||
override val installationScript = PythonHelpersLocator.findPathInHelpers("pycharm_poetry_installer.py")
|
||||
|
||||
override fun onShown() {
|
||||
val moduleDir = when (moduleOrProject) {
|
||||
@@ -58,50 +38,16 @@ class PoetryNewEnvironmentCreator(model: PythonMutableTargetAddInterpreterModel,
|
||||
}
|
||||
|
||||
basePythonComboBox.setItems(validatedInterpreters)
|
||||
|
||||
//val savedPath = PropertiesComponent.getInstance().poetryPath
|
||||
//if (savedPath != null) {
|
||||
// model.state.poetryExecutable.set(savedPath)
|
||||
//}
|
||||
//else {
|
||||
// val modalityState = ModalityState.current().asContextElement()
|
||||
// model.scope.launch(Dispatchers.IO) {
|
||||
// val poetryExecutable = detectPoetryExecutable()
|
||||
// withContext(Dispatchers.EDT + modalityState) {
|
||||
// poetryExecutable?.let { model.state.poetryExecutable.set(it.path) }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
||||
|
||||
override fun getOrCreateSdk(): Sdk {
|
||||
if (model is PythonLocalAddInterpreterModel) {
|
||||
PropertiesComponent.getInstance().poetryPath = model.state.poetryExecutable.get().nullize()
|
||||
}
|
||||
|
||||
val selectedBasePython = model.state.baseInterpreter.get()!!
|
||||
val homePath = model.installPythonIfNeeded(selectedBasePython)
|
||||
|
||||
val newSdk = setupPoetrySdkUnderProgress(null,
|
||||
null,
|
||||
model.baseSdks,
|
||||
model.projectPath.value,
|
||||
homePath, false)!!
|
||||
SdkConfigurationUtil.addSdk(newSdk)
|
||||
return newSdk
|
||||
override fun savePathToExecutableToProperties() {
|
||||
PropertiesComponent.getInstance().poetryPath = executable.get().nullize()
|
||||
}
|
||||
|
||||
override fun createStatisticsInfo(target: PythonInterpreterCreationTargets): InterpreterStatisticsInfo {
|
||||
//val statisticsTarget = if (presenter.projectLocationContext is WslContext) InterpreterTarget.TARGET_WSL else target.toStatisticsField()
|
||||
val statisticsTarget = target.toStatisticsField() // todo fix for wsl
|
||||
return InterpreterStatisticsInfo(InterpreterType.POETRY,
|
||||
statisticsTarget,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
//presenter.projectLocationContext is WslContext,
|
||||
false, // todo fix for wsl
|
||||
InterpreterCreationMode.CUSTOM)
|
||||
override fun setupEnvSdk(project: Project?, module: Module?, baseSdks: List<Sdk>, projectPath: String, homePath: String?, installPackages: Boolean): Sdk? =
|
||||
setupPoetrySdkUnderProgress(project, module, baseSdks, projectPath, homePath, installPackages)
|
||||
|
||||
override fun detectExecutable() {
|
||||
model.detectPoetryExecutable()
|
||||
}
|
||||
}
|
||||
@@ -92,8 +92,8 @@ class PythonAddNewEnvironmentPanel(val projectPath: StateFlow<String>, onlyAllow
|
||||
rowsRange {
|
||||
executableSelector(model.state.condaExecutable,
|
||||
validationRequestor,
|
||||
message("sdk.create.conda.executable.path"),
|
||||
message("sdk.create.conda.missing.text"),
|
||||
message("sdk.create.custom.venv.executable.path", "conda"),
|
||||
message("sdk.create.custom.venv.missing.text", "conda"),
|
||||
createInstallCondaFix(model))
|
||||
//.displayLoaderWhen(presenter.detectingCondaExecutable, scope = presenter.scope, uiContext = presenter.uiContext)
|
||||
}.visibleIf(_baseConda)
|
||||
|
||||
@@ -157,7 +157,7 @@ abstract class PythonMutableTargetAddInterpreterModel(params: PyInterpreterModel
|
||||
detectPipEnvExecutable()
|
||||
}
|
||||
|
||||
suspend fun detectPoetryExecutable() {
|
||||
fun detectPoetryExecutable() {
|
||||
// todo this is local case, fix for targets
|
||||
val savedPath = PropertiesComponent.getInstance().poetryPath
|
||||
if (savedPath != null) {
|
||||
@@ -174,7 +174,7 @@ abstract class PythonMutableTargetAddInterpreterModel(params: PyInterpreterModel
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun detectPipEnvExecutable() {
|
||||
fun detectPipEnvExecutable() {
|
||||
// todo this is local case, fix for targets
|
||||
val savedPath = PropertiesComponent.getInstance().pipEnvPath
|
||||
if (savedPath != null) {
|
||||
|
||||
@@ -423,7 +423,7 @@ fun Panel.executableSelector(
|
||||
inline = true)
|
||||
.align(Align.FILL)
|
||||
.component
|
||||
}.visibleIf(executable.equalsTo(UNKNOWN_EXECUTABLE))
|
||||
}.visibleIf(executable.equalsTo(UNKNOWN_EXECUTABLE)).visibleIf(executable.equalsTo(""))
|
||||
|
||||
row(labelText) {
|
||||
textFieldCell = textFieldWithBrowseButton()
|
||||
@@ -451,7 +451,7 @@ fun Panel.executableSelector(
|
||||
}
|
||||
|
||||
internal fun createInstallCondaFix(model: PythonAddInterpreterModel): ActionLink {
|
||||
return ActionLink(message("sdk.create.conda.install.fix")) {
|
||||
return ActionLink(message("sdk.create.custom.venv.install.fix.title", "Miniconda", "")) {
|
||||
PythonSdkFlavor.clearExecutablesCache()
|
||||
CondaInstallManager.installLatest(null)
|
||||
model.scope.launch(model.uiContext) {
|
||||
|
||||
@@ -97,8 +97,7 @@ fun runPoetry(projectPath: Path?, vararg args: String): String {
|
||||
?: throw PyExecutionException(PyBundle.message("python.sdk.poetry.execution.exception.no.poetry.message"), "poetry",
|
||||
emptyList(), ProcessOutput())
|
||||
|
||||
@Suppress("DialogTitleCapitalization")
|
||||
return runCommand(executable, projectPath, PyBundle.message("python.sdk.poetry.execution.exception.error.running.poetry.message"), *args)
|
||||
return runCommand(executable, projectPath, PyBundle.message("sdk.create.custom.venv.run.error.message", "poetry"), *args)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -133,12 +132,11 @@ private fun runCommand(projectPath: Path, command: String, vararg args: String):
|
||||
runProcess()
|
||||
}
|
||||
return with(result) {
|
||||
@Suppress("DialogTitleCapitalization")
|
||||
when {
|
||||
isCancelled ->
|
||||
throw RunCanceledByUserException()
|
||||
exitCode != 0 ->
|
||||
throw PyExecutionException(PyBundle.message("python.sdk.poetry.execution.exception.error.running.poetry.message"), command,
|
||||
throw PyExecutionException(PyBundle.message("sdk.create.custom.venv.run.error.message", "poetry"), command,
|
||||
args.asList(),
|
||||
stdout, stderr, exitCode, emptyList())
|
||||
else -> stdout
|
||||
@@ -154,7 +152,7 @@ internal fun runPoetryInBackground(module: Module, args: List<String>, @NlsSafe
|
||||
runPoetry(sdk, *args.toTypedArray())
|
||||
}
|
||||
catch (e: ExecutionException) {
|
||||
showSdkExecutionException(sdk, e, PyBundle.message("python.sdk.poetry.execution.exception.error.running.poetry.message"))
|
||||
showSdkExecutionException(sdk, e, PyBundle.message("sdk.create.custom.venv.run.error.message", "poetry"))
|
||||
}
|
||||
finally {
|
||||
PythonSdkUtil.getSitePackagesDirectory(sdk)?.refresh(true, true)
|
||||
|
||||
Reference in New Issue
Block a user