aboutsummaryrefslogtreecommitdiffstats
path: root/lua/mason-core/managers/pip3/init.lua
diff options
context:
space:
mode:
authorWilliam Boman <william@redwill.se>2022-07-08 18:34:38 +0200
committerGitHub <noreply@github.com>2022-07-08 18:34:38 +0200
commit976aa4fbee8a070f362cab6f6ec84e9251a90cf9 (patch)
tree5e8d9c9c59444a25c7801b8f39763c4ba6e1f76d /lua/mason-core/managers/pip3/init.lua
parentfeat: add gotests, gomodifytags, impl (#28) (diff)
downloadmason-976aa4fbee8a070f362cab6f6ec84e9251a90cf9.tar
mason-976aa4fbee8a070f362cab6f6ec84e9251a90cf9.tar.gz
mason-976aa4fbee8a070f362cab6f6ec84e9251a90cf9.tar.bz2
mason-976aa4fbee8a070f362cab6f6ec84e9251a90cf9.tar.lz
mason-976aa4fbee8a070f362cab6f6ec84e9251a90cf9.tar.xz
mason-976aa4fbee8a070f362cab6f6ec84e9251a90cf9.tar.zst
mason-976aa4fbee8a070f362cab6f6ec84e9251a90cf9.zip
refactor: add mason-schemas and mason-core modules (#29)
* refactor: add mason-schemas and move generated filetype map to mason-lspconfig * refactor: add mason-core module
Diffstat (limited to 'lua/mason-core/managers/pip3/init.lua')
-rw-r--r--lua/mason-core/managers/pip3/init.lua175
1 files changed, 175 insertions, 0 deletions
diff --git a/lua/mason-core/managers/pip3/init.lua b/lua/mason-core/managers/pip3/init.lua
new file mode 100644
index 00000000..9502e89e
--- /dev/null
+++ b/lua/mason-core/managers/pip3/init.lua
@@ -0,0 +1,175 @@
+local _ = require "mason-core.functional"
+local settings = require "mason.settings"
+local process = require "mason-core.process"
+local path = require "mason-core.path"
+local platform = require "mason-core.platform"
+local Optional = require "mason-core.optional"
+local installer = require "mason-core.installer"
+local Result = require "mason-core.result"
+local spawn = require "mason-core.spawn"
+
+local VENV_DIR = "venv"
+
+local M = {}
+
+local create_bin_path = _.compose(path.concat, function(executable)
+ return _.append(executable, { VENV_DIR, platform.is_win and "Scripts" or "bin" })
+end, _.if_else(_.always(platform.is.win), _.format "%s.exe", _.identity))
+
+---@param packages string[]
+local function with_receipt(packages)
+ return function()
+ local ctx = installer.context()
+ ctx.receipt:with_primary_source(ctx.receipt.pip3(packages[1]))
+ for i = 2, #packages do
+ ctx.receipt:with_secondary_source(ctx.receipt.pip3(packages[i]))
+ end
+ end
+end
+
+---@async
+---@param packages { [number]: string, bin: string[] | nil } @The pip packages to install. The first item in this list will be the recipient of the requested version, if set.
+function M.packages(packages)
+ return function()
+ return M.install(packages).with_receipt()
+ end
+end
+
+---@async
+---@param packages { [number]: string, bin: string[] | nil } @The pip packages to install. The first item in this list will be the recipient of the requested version, if set.
+function M.install(packages)
+ local ctx = installer.context()
+ local pkgs = _.list_copy(packages)
+
+ ctx.requested_version:if_present(function(version)
+ pkgs[1] = ("%s==%s"):format(pkgs[1], version)
+ end)
+
+ local executables = platform.is_win and _.list_not_nil(vim.g.python3_host_prog, "python", "python3")
+ or _.list_not_nil(vim.g.python3_host_prog, "python3", "python")
+
+ -- pip3 will hardcode the full path to venv executables, so we need to promote cwd to make sure pip uses the final destination path.
+ ctx:promote_cwd()
+
+ -- Find first executable that manages to create venv
+ local executable = _.find_first(function(executable)
+ return pcall(ctx.spawn[executable], { "-m", "venv", VENV_DIR })
+ end, executables)
+
+ Optional.of_nilable(executable)
+ :if_present(function()
+ ctx.spawn.python {
+ "-m",
+ "pip",
+ "--disable-pip-version-check",
+ "install",
+ "-U",
+ settings.current.pip.install_args,
+ pkgs,
+ with_paths = { M.venv_path(ctx.cwd:get()) },
+ }
+ end)
+ :or_else_throw "Unable to create python3 venv environment."
+
+ if packages.bin then
+ _.each(function(bin)
+ ctx:link_bin(bin, create_bin_path(bin))
+ end, packages.bin)
+ end
+
+ return {
+ with_receipt = with_receipt(packages),
+ }
+end
+
+---@param pkg string
+---@return string
+function M.normalize_package(pkg)
+ -- https://stackoverflow.com/a/60307740
+ local s = pkg:gsub("%[.*%]", "")
+ return s
+end
+
+---@async
+---@param receipt InstallReceipt
+---@param install_dir string
+function M.check_outdated_primary_package(receipt, install_dir)
+ if receipt.primary_source.type ~= "pip3" then
+ return Result.failure "Receipt does not have a primary source of type pip3"
+ end
+ local normalized_package = M.normalize_package(receipt.primary_source.package)
+ return spawn
+ .python({
+ "-m",
+ "pip",
+ "list",
+ "--outdated",
+ "--format=json",
+ cwd = install_dir,
+ with_paths = { M.venv_path(install_dir) },
+ })
+ :map_catching(function(result)
+ ---@alias PipOutdatedPackage {name: string, version: string, latest_version: string}
+ ---@type PipOutdatedPackage[]
+ local packages = vim.json.decode(result.stdout)
+
+ local outdated_primary_package = _.find_first(function(outdated_package)
+ return outdated_package.name == normalized_package
+ and outdated_package.version ~= outdated_package.latest_version
+ end, packages)
+
+ return Optional.of_nilable(outdated_primary_package)
+ :map(function(pkg)
+ return {
+ name = normalized_package,
+ current_version = assert(pkg.version),
+ latest_version = assert(pkg.latest_version),
+ }
+ end)
+ :or_else_throw "Primary package is not outdated."
+ end)
+end
+
+---@async
+---@param receipt InstallReceipt
+---@param install_dir string
+function M.get_installed_primary_package_version(receipt, install_dir)
+ if receipt.primary_source.type ~= "pip3" then
+ return Result.failure "Receipt does not have a primary source of type pip3"
+ end
+ return spawn
+ .python({
+ "-m",
+ "pip",
+ "list",
+ "--format=json",
+ cwd = install_dir,
+ with_paths = { M.venv_path(install_dir) },
+ })
+ :map_catching(function(result)
+ local pip_packages = vim.json.decode(result.stdout)
+ local normalized_pip_package = M.normalize_package(receipt.primary_source.package)
+ local pip_package = _.find_first(function(pkg)
+ return pkg.name == normalized_pip_package
+ end, pip_packages)
+ return Optional.of_nilable(pip_package)
+ :map(function(pkg)
+ return pkg.version
+ end)
+ :or_else_throw "Unable to find pip package."
+ end)
+end
+
+---@param install_dir string
+function M.env(install_dir)
+ return {
+ PATH = process.extend_path { M.venv_path(install_dir) },
+ }
+end
+
+---@param install_dir string
+function M.venv_path(install_dir)
+ return path.concat { install_dir, VENV_DIR, platform.is_win and "Scripts" or "bin" }
+end
+
+return M