Skip to content

Commit db211a4

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. fix: ensure the virtual environment is .venv for uv
1 parent e2f7f90 commit db211a4

File tree

6 files changed

+76
-22
lines changed

6 files changed

+76
-22
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: 60 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,18 @@ 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

16-
local VENV_DIR = "venv"
17+
local use_uv = settings.current.pip.use_uv
18+
local VENV_DIR
19+
if use_uv then
20+
VENV_DIR = ".venv"
21+
else
22+
VENV_DIR = "venv"
23+
end
1724

1825
---@async
1926
---@param candidates string[]
@@ -22,11 +29,20 @@ local function resolve_python3(candidates)
2229
a.scheduler()
2330
local available_candidates = _.filter(is_executable, candidates)
2431
for __, candidate in ipairs(available_candidates) do
25-
---@type string
26-
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+)")
28-
if ok then
29-
return { executable = candidate, version = version }
32+
if use_uv and candidate == "uv" then
33+
---@type string
34+
local version_output = spawn[candidate]({ "--version" }):map(_.prop "stdout"):get_or_else ""
35+
local ok, version = pcall(semver.new, version_output:match "uv (%d+.%d+.%d+).*")
36+
if ok then
37+
return { executable = candidate, version = version }
38+
end
39+
elseif not use_uv then
40+
---@type string
41+
local version_output = spawn[candidate]({ "--version" }):map(_.prop "stdout"):get_or_else ""
42+
local ok, version = pcall(semver.new, version_output:match "Python (3%.%d+.%d+)")
43+
if ok then
44+
return { executable = candidate, version = version }
45+
end
3046
end
3147
end
3248
return nil
@@ -76,14 +92,14 @@ local function create_venv(pkg)
7692
local supported_python_versions = providers.pypi.get_supported_python_versions(pkg.name, pkg.version):get_or_nil()
7793

7894
-- 1. Resolve stock python3 installation.
79-
local stock_candidates = platform.is.win and { "python", "python3" } or { "python3", "python" }
95+
local stock_candidates = platform.is.win and { "python", "python3", "uv" } or { "python3", "python", "uv" }
8096
local stock_target = resolve_python3(stock_candidates)
8197
if stock_target then
8298
log.fmt_debug("Resolved stock python3 installation version %s", stock_target.version)
8399
end
84100

85101
-- 2. Resolve suitable versioned python3 installation (python3.12, python3.11, etc.).
86-
local versioned_candidates = {}
102+
local versioned_candidates = { "uv" }
87103
if supported_python_versions ~= nil then
88104
if stock_target and not pep440_check_version(tostring(stock_target.version), supported_python_versions) then
89105
log.fmt_debug("Finding versioned candidates for %s", supported_python_versions)
@@ -103,7 +119,8 @@ local function create_venv(pkg)
103119
-- 3. If a versioned python3 installation was not found, warn the user if the stock python3 installation is outside
104120
-- the supported version range.
105121
if
106-
target == stock_target
122+
use_uv == false
123+
and target == stock_target
107124
and supported_python_versions ~= nil
108125
and not pep440_check_version(tostring(target.version), supported_python_versions)
109126
then
@@ -125,9 +142,14 @@ local function create_venv(pkg)
125142
end
126143
end
127144

128-
log.fmt_debug("Found python3 installation version=%s, executable=%s", target.version, target.executable)
129145
ctx.stdio_sink.stdout "Creating virtual environment…\n"
130-
return ctx.spawn[target.executable] { "-m", "venv", "--system-site-packages", VENV_DIR }
146+
if use_uv then
147+
log.fmt_debug("Found uv installation version=%s, executable=%s", target.version, target.executable)
148+
return ctx.spawn[target.executable] { "venv", VENV_DIR }
149+
else
150+
log.fmt_debug("Found python3 installation version=%s, executable=%s", target.version, target.executable)
151+
return ctx.spawn[target.executable] { "-m", "venv", "--system-site-packages", VENV_DIR }
152+
end
131153
end
132154

133155
---@param ctx InstallContext
@@ -153,6 +175,9 @@ end
153175
---@param args SpawnArgs
154176
local function venv_python(args)
155177
local ctx = installer.context()
178+
if use_uv then
179+
return ctx.spawn[{ "uv", "venv" }](args)
180+
end
156181
return find_venv_executable(ctx, "python"):and_then(function(python_path)
157182
return ctx.spawn[path.concat { ctx.cwd:get(), python_path }](args)
158183
end)
@@ -162,16 +187,29 @@ end
162187
---@param pkgs string[]
163188
---@param extra_args? string[]
164189
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-
}
190+
if use_uv then
191+
local ctx = installer.context()
192+
193+
local task = ctx.spawn["uv"] {
194+
"pip",
195+
"install",
196+
"-U",
197+
extra_args or vim.NIL,
198+
pkgs,
199+
}
200+
return task
201+
else
202+
return venv_python {
203+
"-m",
204+
"pip",
205+
"--disable-pip-version-check",
206+
"install",
207+
"--ignore-installed",
208+
"-U",
209+
extra_args or vim.NIL,
210+
pkgs,
211+
}
212+
end
175213
end
176214

177215
---@async
@@ -185,7 +223,7 @@ function M.init(opts)
185223
ctx:promote_cwd()
186224
try(create_venv(opts.package))
187225

188-
if opts.upgrade_pip then
226+
if opts.upgrade_pip and not use_uv then
189227
ctx.stdio_sink.stdout "Upgrading pip inside the virtual environment…\n"
190228
try(pip_install({ "pip" }, opts.install_extra_args))
191229
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)