diff --git a/lua/mason-core/installer/managers/pypi.lua b/lua/mason-core/installer/managers/pypi.lua index e9c0c432c..0f3de1fb5 100644 --- a/lua/mason-core/installer/managers/pypi.lua +++ b/lua/mason-core/installer/managers/pypi.lua @@ -20,17 +20,32 @@ local function create_venv(py_executables) end, py_executables)):ok_or "Failed to create python3 virtual environment." end +---@param ctx InstallContext +---@param executable string +local function find_venv_executable(ctx, executable) + local candidates = _.filter(_.identity, { + platform.is.unix and path.concat { VENV_DIR, "bin", executable }, + -- MSYS2 + platform.is.win and path.concat { VENV_DIR, "bin", ("%s.exe"):format(executable) }, + -- Stock Windows + platform.is.win and path.concat { VENV_DIR, "Scripts", ("%s.exe"):format(executable) }, + }) + + for _, candidate in ipairs(candidates) do + if ctx.fs:file_exists(candidate) then + return Result.success(candidate) + end + end + return Result.failure(("Failed to find executable %q in Python virtual environment."):format(executable)) +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) + return find_venv_executable(ctx, "python"):and_then(function(python_path) + return ctx.spawn[path.concat { ctx.cwd:get(), python_path }](args) + end) end ---@async @@ -93,16 +108,10 @@ function M.install(pkg, version, opts) }, 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, - }) +---@param executable string +function M.bin_path(executable) + local ctx = installer.context() + return find_venv_executable(ctx, executable) end return M diff --git a/tests/mason-core/installer/managers/pypi_spec.lua b/tests/mason-core/installer/managers/pypi_spec.lua index cb4bc2b37..353606aa5 100644 --- a/tests/mason-core/installer/managers/pypi_spec.lua +++ b/tests/mason-core/installer/managers/pypi_spec.lua @@ -1,4 +1,5 @@ local installer = require "mason-core.installer" +local match = require "luassert.match" local path = require "mason-core.path" local pypi = require "mason-core.installer.managers.pypi" local spy = require "luassert.spy" @@ -35,6 +36,9 @@ describe("pypi manager", function() it("should init venv and upgrade pip", function() local ctx = create_dummy_context() stub(ctx, "promote_cwd") + stub(ctx.fs, "file_exists") + ctx.fs.file_exists.on_call_with(match.ref(ctx.fs), "venv/bin/python").returns(true) + installer.exec_in_context(ctx, function() pypi.init { upgrade_pip = true, install_extra_args = { "--proxy", "http://localhost" } } end) @@ -60,6 +64,8 @@ describe("pypi manager", function() it("should install", function() local ctx = create_dummy_context() + stub(ctx.fs, "file_exists") + ctx.fs.file_exists.on_call_with(match.ref(ctx.fs), "venv/bin/python").returns(true) installer.exec_in_context(ctx, function() pypi.install("pypi-package", "1.0.0") end) @@ -81,6 +87,8 @@ describe("pypi manager", function() it("should write output", function() local ctx = create_dummy_context() + stub(ctx.fs, "file_exists") + ctx.fs.file_exists.on_call_with(match.ref(ctx.fs), "venv/bin/python").returns(true) spy.on(ctx.stdio_sink, "stdout") installer.exec_in_context(ctx, function() @@ -92,6 +100,9 @@ describe("pypi manager", function() it("should install extra specifier", function() local ctx = create_dummy_context() + stub(ctx.fs, "file_exists") + ctx.fs.file_exists.on_call_with(match.ref(ctx.fs), "venv/bin/python").returns(true) + installer.exec_in_context(ctx, function() pypi.install("pypi-package", "1.0.0", { extra = "lsp", @@ -115,6 +126,8 @@ describe("pypi manager", function() it("should install extra packages", function() local ctx = create_dummy_context() + stub(ctx.fs, "file_exists") + ctx.fs.file_exists.on_call_with(match.ref(ctx.fs), "venv/bin/python").returns(true) installer.exec_in_context(ctx, function() pypi.install("pypi-package", "1.0.0", { extra_packages = { "extra-package" }, diff --git a/tests/mason-core/installer/registry/link_spec.lua b/tests/mason-core/installer/registry/link_spec.lua index 662e1323e..eb6af1ccd 100644 --- a/tests/mason-core/installer/registry/link_spec.lua +++ b/tests/mason-core/installer/registry/link_spec.lua @@ -119,7 +119,7 @@ describe("registry linker", function() ["npm:executable"] = "node_modules/.bin/executable", ["nuget:executable"] = "executable", ["opam:executable"] = "bin/executable", - ["pypi:executable"] = "venv/bin/executable", + -- ["pypi:executable"] = "venv/bin/executable", } for bin, path in pairs(matrix) do