diff options
| author | William Boman <william@redwill.se> | 2022-07-08 18:34:38 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-07-08 18:34:38 +0200 |
| commit | 976aa4fbee8a070f362cab6f6ec84e9251a90cf9 (patch) | |
| tree | 5e8d9c9c59444a25c7801b8f39763c4ba6e1f76d /lua/mason-core/managers/pip3/init.lua | |
| parent | feat: add gotests, gomodifytags, impl (#28) (diff) | |
| download | mason-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.lua | 175 |
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 |
