Skip to content

Commit 4fbe794

Browse files
feat: add uv as installer for python
UV is a very fast installer for python packages that can be 10-100x faster to resolve packages. This adds an option for Mason to use it instead of pip to resolve python packages that are installed via Mason. More info about the replacement: https://github.com/astral-sh/uv I have no relationship with uv, it is just very fast and it would be nice to have updates for packages like sqlfluff take a lot less time than they currently do to resolve during updates.
1 parent e2f7f90 commit 4fbe794

File tree

6 files changed

+67
-19
lines changed

6 files changed

+67
-19
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,10 @@ local DEFAULT_SETTINGS = {
252252
-- Whether to upgrade pip to the latest version in the virtual environment before installing packages.
253253
upgrade_pip = false,
254254

255+
---@since 1.8.0
256+
-- Whether to use uv to install packages instead of pip
257+
use_uv = false,
258+
255259
---@since 1.0.0
256260
-- These args will be added to `pip install` calls. Note that setting extra args might impact intended behavior
257261
-- and is not recommended.

doc/mason.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,10 @@ Example:
314314
-- Whether to upgrade pip to the latest version in the virtual environment before installing packages.
315315
upgrade_pip = false,
316316

317+
---@since 1.8.0
318+
-- Whether to use uv to install packages instead of pip
319+
use_uv = false,
320+
317321
---@since 1.0.0
318322
-- These args will be added to `pip install` calls. Note that setting extra args might impact intended behavior
319323
-- and is not recommended.

lua/mason-core/installer/managers/pypi.lua

Lines changed: 51 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,32 @@ local pep440 = require "mason-core.pep440"
99
local platform = require "mason-core.platform"
1010
local providers = require "mason-core.providers"
1111
local semver = require "mason-core.semver"
12+
local settings = require "mason.settings"
1213
local spawn = require "mason-core.spawn"
1314

1415
local M = {}
1516

17+
local use_uv = settings.current.pip.use_uv
1618
local VENV_DIR = "venv"
1719

1820
---@async
1921
---@param candidates string[]
2022
local function resolve_python3(candidates)
2123
local is_executable = _.compose(_.equals(1), vim.fn.executable)
2224
a.scheduler()
25+
if use_uv then
26+
candidates = { "uv" }
27+
end
2328
local available_candidates = _.filter(is_executable, candidates)
2429
for __, candidate in ipairs(available_candidates) do
2530
---@type string
2631
local version_output = spawn[candidate]({ "--version" }):map(_.prop "stdout"):get_or_else ""
27-
local ok, version = pcall(semver.new, version_output:match "Python (3%.%d+.%d+)")
32+
local ok, version
33+
if use_uv then
34+
ok, version = pcall(semver.new, version_output:match "uv (%d+.%d+.%d+)")
35+
else
36+
ok, version = pcall(semver.new, version_output:match "Python (3%.%d+.%d+)")
37+
end
2838
if ok then
2939
return { executable = candidate, version = version }
3040
end
@@ -61,10 +71,10 @@ local function get_versioned_candidates(supported_python_versions)
6171
{ semver.new "3.12.0", "python3.12" },
6272
{ semver.new "3.11.0", "python3.11" },
6373
{ semver.new "3.10.0", "python3.10" },
64-
{ semver.new "3.9.0", "python3.9" },
65-
{ semver.new "3.8.0", "python3.8" },
66-
{ semver.new "3.7.0", "python3.7" },
67-
{ semver.new "3.6.0", "python3.6" },
74+
{ semver.new "3.9.0", "python3.9" },
75+
{ semver.new "3.8.0", "python3.8" },
76+
{ semver.new "3.7.0", "python3.7" },
77+
{ semver.new "3.6.0", "python3.6" },
6878
})
6979
end
7080

@@ -109,15 +119,17 @@ local function create_venv(pkg)
109119
then
110120
if ctx.opts.force then
111121
ctx.stdio_sink.stderr(
112-
("Warning: The resolved python3 version %s is not compatible with the required Python versions: %s.\n"):format(
122+
("Warning: The resolved python3 version %s is not compatible with the required Python versions: %s.\n")
123+
:format(
113124
target.version,
114125
supported_python_versions
115126
)
116127
)
117128
else
118129
ctx.stdio_sink.stderr "Run with :MasonInstall --force to bypass this version validation.\n"
119130
return Result.failure(
120-
("Failed to find a python3 installation in PATH that meets the required versions (%s). Found version: %s."):format(
131+
("Failed to find a python3 installation in PATH that meets the required versions (%s). Found version: %s.")
132+
:format(
121133
supported_python_versions,
122134
target.version
123135
)
@@ -127,7 +139,14 @@ local function create_venv(pkg)
127139

128140
log.fmt_debug("Found python3 installation version=%s, executable=%s", target.version, target.executable)
129141
ctx.stdio_sink.stdout "Creating virtual environment…\n"
130-
return ctx.spawn[target.executable] { "-m", "venv", "--system-site-packages", VENV_DIR }
142+
143+
if use_uv then
144+
log.fmt_debug("Found uv installation version=%s, executable=%s", target.version, target.executable)
145+
return ctx.spawn[target.executable] { "venv", VENV_DIR }
146+
else
147+
log.fmt_debug("Found python3 installation version=%s, executable=%s", target.version, target.executable)
148+
return ctx.spawn[target.executable] { "-m", "venv", "--system-site-packages", VENV_DIR }
149+
end
131150
end
132151

133152
---@param ctx InstallContext
@@ -162,16 +181,29 @@ end
162181
---@param pkgs string[]
163182
---@param extra_args? string[]
164183
local function pip_install(pkgs, extra_args)
165-
return venv_python {
166-
"-m",
167-
"pip",
168-
"--disable-pip-version-check",
169-
"install",
170-
"--ignore-installed",
171-
"-U",
172-
extra_args or vim.NIL,
173-
pkgs,
174-
}
184+
if use_uv then
185+
local ctx = installer.context()
186+
local task = ctx.spawn["uv"] {
187+
"pip",
188+
"install",
189+
"-U",
190+
extra_args or vim.NIL,
191+
pkgs,
192+
}
193+
-- vim.api.nvim_set_current_dir(curdir)
194+
return task
195+
else
196+
return venv_python {
197+
"-m",
198+
"pip",
199+
"--disable-pip-version-check",
200+
"install",
201+
"--ignore-installed",
202+
"-U",
203+
extra_args or vim.NIL,
204+
pkgs,
205+
}
206+
end
175207
end
176208

177209
---@async
@@ -185,7 +217,7 @@ function M.init(opts)
185217
ctx:promote_cwd()
186218
try(create_venv(opts.package))
187219

188-
if opts.upgrade_pip then
220+
if opts.upgrade_pip and not use_uv then
189221
ctx.stdio_sink.stdout "Upgrading pip inside the virtual environment…\n"
190222
try(pip_install({ "pip" }, opts.install_extra_args))
191223
end

lua/mason-core/installer/registry/providers/pypi.lua

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ function M.parse(source, purl)
2727
pip = {
2828
upgrade = settings.current.pip.upgrade_pip,
2929
extra_args = settings.current.pip.install_args,
30+
use_uv = settings.current.pip.use_uv,
3031
},
3132
}
3233

@@ -48,11 +49,13 @@ function M.install(ctx, source)
4849
},
4950
upgrade_pip = source.pip.upgrade,
5051
install_extra_args = source.pip.extra_args,
52+
use_uv = source.pip.use_uv,
5153
})
5254
try(pypi.install(source.package, source.version, {
5355
extra = source.extra,
5456
extra_packages = source.extra_packages,
5557
install_extra_args = source.pip.extra_args,
58+
use_uv = source.pip.use_uv,
5659
}))
5760
end)
5861
end

lua/mason/settings.lua

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ local DEFAULT_SETTINGS = {
6060
-- Whether to upgrade pip to the latest version in the virtual environment before installing packages.
6161
upgrade_pip = false,
6262

63+
---@since 1.8.0
64+
-- Whether to use uv to install packages instead of pip
65+
use_uv = false,
66+
6367
---@since 1.0.0
6468
-- These args will be added to `pip install` calls. Note that setting extra args might impact intended behavior
6569
-- and is not recommended.

tests/mason-core/installer/registry/providers/pypi_spec.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ describe("pypi provider :: parsing", function()
3131
pip = {
3232
upgrade = true,
3333
extra_args = { "--proxy", "http://localghost" },
34+
use_uv = false,
3435
},
3536
},
3637
pypi.parse({ extra_packages = { "extra" } }, purl())

0 commit comments

Comments
 (0)