mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-15 02:59:33 +07:00
PY-77696, PY-77724 Install UV fixes
1. Rewrite the installation script. Now it returns Path to executable. 2. Save an executable path to Properties. Merge-request: IJ-MR-150390 Merged-by: Egor Eliseev <Egor.Eliseev@jetbrains.com> (cherry picked from commit cafb40528ea0e2613248a37291e76e4d24e34674) GitOrigin-RevId: 911afa3ee4c2e38231cc58f19337f2b7125bea44
This commit is contained in:
committed by
intellij-monorepo-bot
parent
48d582644a
commit
18f5e952db
@@ -986,7 +986,7 @@ object CommunityLibraryLicenses {
|
||||
.apache("https://github.com/JetBrains/package-search-api-models/blob/master/LICENSE")
|
||||
.suppliedByOrganizations("JetBrains Team"),
|
||||
|
||||
LibraryLicense("pip", version = "20.3.4", attachedTo = "intellij.python", url = "https://pip.pypa.io/")
|
||||
LibraryLicense("pip", version = "24.3.1", attachedTo = "intellij.python", url = "https://pip.pypa.io/")
|
||||
.mit("https://github.com/pypa/pip/blob/main/LICENSE.txt"),
|
||||
|
||||
LibraryLicense("plexus-archiver", libraryName = "plexus-archiver", url = "https://github.com/codehaus-plexus/plexus-archiver")
|
||||
|
||||
Binary file not shown.
BIN
python/helpers/pip-24.3.1-py2.py3-none-any.whl
Normal file
BIN
python/helpers/pip-24.3.1-py2.py3-none-any.whl
Normal file
Binary file not shown.
@@ -13,7 +13,6 @@ It will perform the following steps:
|
||||
* Install a script into a platform-specific path:
|
||||
- `~/.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 `package-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
|
||||
@@ -40,12 +39,17 @@ from pathlib import Path
|
||||
from typing import Optional
|
||||
from urllib.request import Request
|
||||
from urllib.request import urlopen
|
||||
from enum import Enum
|
||||
|
||||
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"
|
||||
|
||||
class PipRunCommand(Enum):
|
||||
PIP = ("-m", "pip")
|
||||
WHL = (f"pip-24.3.1-py2.py3-none-any.whl{os.sep}pip", )
|
||||
|
||||
def data_dir(package_dir_name) -> Path:
|
||||
if WINDOWS:
|
||||
base_dir = Path(_get_win_folder("CSIDL_APPDATA"))
|
||||
@@ -213,9 +217,6 @@ class VirtualEnvironment:
|
||||
|
||||
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
|
||||
@@ -236,9 +237,15 @@ class VirtualEnvironment:
|
||||
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)
|
||||
def pip(self, pip_command, *args, **kwargs) -> subprocess.CompletedProcess:
|
||||
return self.python(*pip_command, *args, **kwargs)
|
||||
|
||||
def find_pip_command(self):
|
||||
try:
|
||||
self.pip(PipRunCommand.PIP.value, "--version")
|
||||
return PipRunCommand.PIP
|
||||
except:
|
||||
return PipRunCommand.WHL
|
||||
|
||||
class Installer:
|
||||
def __init__(
|
||||
@@ -255,6 +262,8 @@ class Installer:
|
||||
|
||||
@property
|
||||
def bin_dir(self) -> Path:
|
||||
if self._path is not None:
|
||||
return Path(self._path).resolve()
|
||||
if not self._bin_dir:
|
||||
self._bin_dir = bin_dir()
|
||||
return self._bin_dir
|
||||
@@ -265,8 +274,8 @@ class Installer:
|
||||
self._data_dir = data_dir(self._name)
|
||||
return self._data_dir
|
||||
|
||||
def run(self) -> int:
|
||||
install_version = self._path if self._path is not None else self._version
|
||||
def run(self):
|
||||
install_version = self._version
|
||||
|
||||
self.display_pre_message()
|
||||
self.ensure_directories()
|
||||
@@ -281,6 +290,9 @@ class Installer:
|
||||
self._write("")
|
||||
self.display_post_message_and_add_to_path(install_version)
|
||||
|
||||
execFile = "{package}.exe".format(package=self._name) if WINDOWS else "{package}".format(package=self._name)
|
||||
self._write(str(self.bin_dir.joinpath(execFile)))
|
||||
|
||||
return 0
|
||||
|
||||
def install(self, version):
|
||||
@@ -360,14 +372,12 @@ class Installer:
|
||||
def install_package(self, version: str, env: VirtualEnvironment) -> None:
|
||||
self._install_comment(version, "Installing {package}".format(package=self._name))
|
||||
|
||||
if self._path:
|
||||
specification = version
|
||||
elif version is not None:
|
||||
if version is not None:
|
||||
specification = "{package}=={version}".format(package=self._name, version=version)
|
||||
else:
|
||||
specification = "{package}".format(package=self._name)
|
||||
|
||||
env.pip("install", specification)
|
||||
env.pip(env.find_pip_command().value, "install", specification)
|
||||
|
||||
def display_pre_message(self) -> None:
|
||||
kwargs = {
|
||||
@@ -380,13 +390,16 @@ class Installer:
|
||||
if version is None:
|
||||
version = "latest"
|
||||
|
||||
if WINDOWS:
|
||||
return self.display_post_message_windows_and_add_to_path(version)
|
||||
try:
|
||||
if WINDOWS:
|
||||
return self.display_post_message_windows_and_add_to_path(version)
|
||||
|
||||
if SHELL == "fish":
|
||||
return self.display_post_message_fish_and_add_to_path(version)
|
||||
if SHELL == "fish":
|
||||
return self.display_post_message_fish_and_add_to_path(version)
|
||||
|
||||
return self.display_post_message_unix(version)
|
||||
return self.display_post_message_unix(version)
|
||||
except:
|
||||
pass
|
||||
|
||||
def display_post_message_windows_and_add_to_path(self, version: str) -> None:
|
||||
path = self.get_windows_path_var()
|
||||
@@ -472,17 +485,8 @@ def main():
|
||||
description="Installs the latest (or given) version of package"
|
||||
)
|
||||
parser.add_argument("-n", "--name", required=True)
|
||||
parser.add_argument("--version", help="install named version", dest="version")
|
||||
parser.add_argument(
|
||||
"--path",
|
||||
dest="path",
|
||||
action="store",
|
||||
help=(
|
||||
"Install from a given path (file or directory) instead of "
|
||||
"fetching the latest version of package available online."
|
||||
),
|
||||
)
|
||||
|
||||
parser.add_argument("-v", "--version", help="install named version", dest="version")
|
||||
parser.add_argument("-p", "--path")
|
||||
args = parser.parse_args()
|
||||
|
||||
installer = Installer(
|
||||
|
||||
@@ -36,7 +36,7 @@ import static com.intellij.webcore.packaging.PackageVersionComparator.VERSION_CO
|
||||
|
||||
public abstract class PyPackageManagerImplBase extends PyPackageManager {
|
||||
protected static final String SETUPTOOLS_VERSION = "44.1.1";
|
||||
protected static final String PIP_VERSION = "20.3.4";
|
||||
protected static final String PIP_VERSION = "24.3.1";
|
||||
|
||||
protected static final String SETUPTOOLS_WHEEL_NAME = "setuptools-" + SETUPTOOLS_VERSION + "-py2.py3-none-any.whl";
|
||||
protected static final String PIP_WHEEL_NAME = "pip-" + PIP_VERSION + "-py2.py3-none-any.whl";
|
||||
|
||||
@@ -1,25 +1,9 @@
|
||||
// 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.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
|
||||
|
||||
@@ -31,95 +15,16 @@ import kotlin.io.path.absolutePathString
|
||||
@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.
|
||||
*/
|
||||
internal suspend fun installPackageWithPython(url: URL, pythonExecutable: String): Result<String> {
|
||||
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
|
||||
suspend 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
|
||||
suspend 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()
|
||||
}
|
||||
|
||||
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.
|
||||
* @return executable [Path]
|
||||
*/
|
||||
@Internal
|
||||
suspend fun installExecutableViaPythonScript(scriptPath: Path, pythonExecutable: String, vararg args: String) =
|
||||
runCommandLine(GeneralCommandLine(pythonExecutable, scriptPath.absolutePathString(), *args)).getOrThrow()
|
||||
suspend fun installExecutableViaPythonScript(scriptPath: Path, pythonExecutable: String, vararg args: String): Result<Path> {
|
||||
val result = runCommandLine(GeneralCommandLine(pythonExecutable, scriptPath.absolutePathString(), *args)).getOrElse { return Result.failure(it) }
|
||||
return Result.success(Path.of(result.split("\n").last()))
|
||||
}
|
||||
@@ -52,7 +52,7 @@ abstract class CustomNewEnvironmentCreator(private val name: String, model: Pyth
|
||||
}
|
||||
|
||||
override suspend fun getOrCreateSdk(moduleOrProject: ModuleOrProject): Result<Sdk> {
|
||||
savePathToExecutableToProperties()
|
||||
savePathToExecutableToProperties(null)
|
||||
|
||||
// todo think about better error handling
|
||||
val selectedBasePython = model.state.baseInterpreter.get()!!
|
||||
@@ -117,10 +117,11 @@ abstract class CustomNewEnvironmentCreator(private val name: String, model: Pyth
|
||||
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, "-n", name)
|
||||
val executablePath = installExecutableViaPythonScript(installationScript, pythonExecutable, "-n", name).getOrNull()
|
||||
if (executablePath != null) {
|
||||
savePathToExecutableToProperties(executablePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -136,7 +137,13 @@ abstract class CustomNewEnvironmentCreator(private val name: String, model: Pyth
|
||||
*/
|
||||
private val installationScript: Path? = PythonHelpersLocator.findPathInHelpers("pycharm_package_installer.py")
|
||||
|
||||
internal abstract fun savePathToExecutableToProperties()
|
||||
|
||||
/**
|
||||
* Saves the provided path to an executable in the properties of the environment
|
||||
*
|
||||
* @param [path] The path to the executable that needs to be saved. This may be null when tries to find automatically.
|
||||
*/
|
||||
internal abstract fun savePathToExecutableToProperties(path: Path?)
|
||||
|
||||
protected abstract suspend fun setupEnvSdk(project: Project?, module: Module?, baseSdks: List<Sdk>, projectPath: String, homePath: String?, installPackages: Boolean): Result<Sdk>
|
||||
|
||||
|
||||
@@ -10,14 +10,17 @@ import com.intellij.util.text.nullize
|
||||
import com.jetbrains.python.sdk.pipenv.pipEnvPath
|
||||
import com.jetbrains.python.sdk.pipenv.setupPipEnvSdkUnderProgress
|
||||
import com.jetbrains.python.statistics.InterpreterType
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.pathString
|
||||
|
||||
|
||||
class EnvironmentCreatorPip(model: PythonMutableTargetAddInterpreterModel) : CustomNewEnvironmentCreator("pipenv", model) {
|
||||
override val interpreterType: InterpreterType = InterpreterType.PIPENV
|
||||
override val executable: ObservableMutableProperty<String> = model.state.pipenvExecutable
|
||||
|
||||
override fun savePathToExecutableToProperties() {
|
||||
PropertiesComponent.getInstance().pipEnvPath = executable.get().nullize()
|
||||
override fun savePathToExecutableToProperties(path: Path?) {
|
||||
val savingPath = path?.pathString ?: executable.get().nullize() ?: return
|
||||
PropertiesComponent.getInstance().pipEnvPath = savingPath
|
||||
}
|
||||
|
||||
override suspend fun setupEnvSdk(project: Project?, module: Module?, baseSdks: List<Sdk>, projectPath: String, homePath: String?, installPackages: Boolean): Result<Sdk> =
|
||||
|
||||
@@ -27,11 +27,11 @@ import com.jetbrains.python.sdk.poetry.poetryToml
|
||||
import com.jetbrains.python.sdk.poetry.pyProjectToml
|
||||
import com.jetbrains.python.sdk.poetry.setupPoetrySdkUnderProgress
|
||||
import com.jetbrains.python.statistics.InterpreterType
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.pathString
|
||||
|
||||
class EnvironmentCreatorPoetry(model: PythonMutableTargetAddInterpreterModel, private val moduleOrProject: ModuleOrProject?) : CustomNewEnvironmentCreator("poetry", model) {
|
||||
override val interpreterType: InterpreterType = InterpreterType.POETRY
|
||||
@@ -61,8 +61,9 @@ class EnvironmentCreatorPoetry(model: PythonMutableTargetAddInterpreterModel, pr
|
||||
basePythonComboBox.setItems(validatedInterpreters)
|
||||
}
|
||||
|
||||
override fun savePathToExecutableToProperties() {
|
||||
PropertiesComponent.getInstance().poetryPath = executable.get().nullize()
|
||||
override fun savePathToExecutableToProperties(path: Path?) {
|
||||
val savingPath = path?.pathString ?: executable.get().nullize() ?: return
|
||||
PropertiesComponent.getInstance().poetryPath = savingPath
|
||||
}
|
||||
|
||||
override suspend fun setupEnvSdk(project: Project?, module: Module?, baseSdks: List<Sdk>, projectPath: String, homePath: String?, installPackages: Boolean): Result<Sdk> {
|
||||
|
||||
@@ -5,6 +5,7 @@ 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.util.text.nullize
|
||||
import com.jetbrains.python.sdk.ModuleOrProject
|
||||
import com.jetbrains.python.sdk.uv.impl.setUvExecutable
|
||||
import com.jetbrains.python.sdk.uv.setupUvSdkUnderProgress
|
||||
@@ -20,8 +21,9 @@ class EnvironmentCreatorUv(model: PythonMutableTargetAddInterpreterModel, privat
|
||||
basePythonComboBox.setItems(model.baseInterpreters)
|
||||
}
|
||||
|
||||
override fun savePathToExecutableToProperties() {
|
||||
setUvExecutable(Path.of(executable.get()))
|
||||
override fun savePathToExecutableToProperties(path: Path?) {
|
||||
val savingPath = path ?: executable.get().nullize()?.let { Path.of(it) } ?: return
|
||||
setUvExecutable(savingPath)
|
||||
}
|
||||
|
||||
override suspend fun setupEnvSdk(project: Project?, module: Module?, baseSdks: List<Sdk>, projectPath: String, homePath: String?, installPackages: Boolean): Result<Sdk> {
|
||||
|
||||
Reference in New Issue
Block a user