local Optional = require "mason-core.optional" local Result = require "mason-core.result" local _ = require "mason-core.functional" local a = require "mason-core.async" local installer = require "mason-core.installer" local log = require "mason-core.log" local path = require "mason-core.path" local platform = require "mason-core.platform" local M = {} local VENV_DIR = "venv" ---@async ---@param py_executables string[] local function create_venv(py_executables) local ctx = installer.context() return Optional.of_nilable(_.find_first(function(executable) return ctx.spawn[executable]({ "-m", "venv", VENV_DIR }):is_success() end, py_executables)):ok_or "Failed to create python3 virtual environment." end ---@async ---@param args SpawnArgs local function venv_python(args) local ctx = installer.context() local python_path = path.concat { ctx.cwd:get(), VENV_DIR, platform.is.win and "Scripts" or "bin", platform.is.win and "python.exe" or "python", } return ctx.spawn[python_path](args) end ---@async ---@param pkgs string[] ---@param extra_args? string[] local function pip_install(pkgs, extra_args) return venv_python { "-m", "pip", "--disable-pip-version-check", "install", "-U", extra_args or vim.NIL, pkgs, } end ---@async ---@param opts { upgrade_pip: boolean, install_extra_args?: string[] } function M.init(opts) return Result.try(function(try) log.fmt_debug("pypi: init", opts) local ctx = installer.context() a.scheduler() local executables = platform.is.win and _.list_not_nil( vim.g.python3_host_prog and vim.fn.expand(vim.g.python3_host_prog), "python", "python3" ) or _.list_not_nil(vim.g.python3_host_prog and vim.fn.expand(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() try(create_venv(executables)) if opts.upgrade_pip then try(pip_install({ "pip" }, opts.install_extra_args)) end end) end ---@async ---@param pkg string ---@param version string ---@param opts? { extra?: string, extra_packages?: string[], install_extra_args?: string[] } function M.install(pkg, version, opts) opts = opts or {} log.fmt_debug("pypi: install %s %s", pkg, version, opts) return pip_install({ opts.extra and ("%s[%s]==%s"):format(pkg, opts.extra, version) or ("%s==%s"):format(pkg, version), opts.extra_packages or vim.NIL, }, opts.install_extra_args) end ---@param exec string function M.bin_path(exec) return Result.pcall(platform.when, { unix = function() return path.concat { "venv", "bin", exec } end, win = function() return path.concat { "venv", "Scripts", ("%s.exe"):format(exec) } end, }) end return M