11import logging
2+ import platform
3+ import re
24import sys
35import nox
46
1012
1113nox .options .default_venv_backend = "uv"
1214
15+ BIN_STALL_CARGO = re .compile (r"^([^\s]+)\s([^:]+):" )
16+ CARGO_BINSTALL = "cargo-binstall"
17+
18+
19+ class CargoInstaller :
20+ def __init__ (self , session : nox .Session ):
21+ cargo_installed : str | None = session .run (
22+ "cargo" , "install" , "--list" , external = True , silent = True
23+ )
24+ assert cargo_installed is not None , "Is rust cargo installed?"
25+ cargo_bins : dict [str , str ] = {}
26+ for line in cargo_installed .splitlines ():
27+ found = BIN_STALL_CARGO .match (line )
28+ if found is not None :
29+ name , version = found .groups ()
30+ cargo_bins .setdefault (name , version )
31+ self .cargo_bins = cargo_bins
32+ self .cargo_install_cmd : tuple [str , ...] = ("cargo" , "install" )
33+
34+ if CARGO_BINSTALL in cargo_bins :
35+ ci_logger .info (
36+ "Found %s: %s" % (CARGO_BINSTALL , cargo_bins [CARGO_BINSTALL ])
37+ )
38+ self .cargo_install_cmd = ("cargo" , "binstall" , "-y" )
39+ else :
40+ ci_logger .info ("Installing %s" % CARGO_BINSTALL )
41+ match platform .system ():
42+ case "Windows" :
43+ one_liner = """Set-ExecutionPolicy Unrestricted -Scope Process; iex (iwr "https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.ps1").Content"""
44+ session .run (* one_liner .split (), external = True )
45+ case "Linux" | "Darwin" :
46+ one_liner = "curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash"
47+ session .run (* one_liner .split (), external = True )
48+ case _:
49+ session .run (
50+ * self .cargo_install_cmd ,
51+ CARGO_BINSTALL ,
52+ "--locked" ,
53+ external = True ,
54+ )
55+
56+ def check_install (self , req : str , session : nox .Session ):
57+ """Use cargo to ensure `req` is installed.
58+
59+ Parameters:
60+ req: The package name (on crates.io) to check and install.
61+ Supports explicit version in the form
62+ 63+ Typically this is the project's name as published on crates.io.
64+ """
65+
66+ ver = None
67+ if "@" in req :
68+ req , ver = req .split ("@" , 1 )[:2 ]
69+
70+ def install ():
71+ ci_logger .info ("Installing %s" % req )
72+ dep = req if not ver else f"{ req } @{ ver } "
73+ session .run (* self .cargo_install_cmd , dep , "--locked" , external = True )
74+
75+ installed = False
76+ if req in self .cargo_bins :
77+ ci_logger .info ("Found %s %s" % (req , self .cargo_bins [req ]))
78+ installed = True
79+ if ver or not installed :
80+ install ()
81+
1382
1483def uv_sync (session : nox .Session , * args : str ):
1584 session .run_install (
@@ -79,6 +148,13 @@ def test(session: nox.Session):
79148 Otherwise, the default profile is used.
80149 """
81150 uv_sync (session , "--group" , "test" )
151+ installer = CargoInstaller (session )
152+ for req in [
153+ "cargo-llvm-cov" ,
154+ "cargo-nextest" ,
155+ ]:
156+ installer .check_install (req , session )
157+
82158 session .run (
83159 "cargo" ,
84160 "llvm-cov" ,
@@ -102,6 +178,8 @@ def test_clean(session: nox.Session):
102178 This is useful if coverage data needs to be
103179 completely refreshed.
104180 """
181+ installer = CargoInstaller (session )
182+ installer .check_install ("cargo-llvm-cov" , session )
105183 session .run ("cargo" , "llvm-cov" , "clean" , external = True )
106184
107185
@@ -114,6 +192,8 @@ def llvm_cov(session: nox.Session):
114192
115193 Otherwise, the default profile is used.
116194 """
195+ installer = CargoInstaller (session )
196+ installer .check_install ("cargo-llvm-cov" , session )
117197 session .run (
118198 "cargo" ,
119199 "llvm-cov" ,
@@ -135,6 +215,9 @@ def pretty_cov(session: nox.Session):
135215
136216 Otherwise, the default profile is used.
137217 """
218+ installer = CargoInstaller (session )
219+ for req in ["cargo-llvm-cov" , "llvm-cov-pretty" ]:
220+ installer .check_install (req , session )
138221 session .run (
139222 "cargo" ,
140223 "llvm-cov" ,
@@ -156,6 +239,8 @@ def lcov(session: nox.Session):
156239 Useful for codecov uploads and VSCode extensions
157240 like "Coverage Gutters".
158241 """
242+ installer = CargoInstaller (session )
243+ installer .check_install ("cargo-llvm-cov" , session )
159244 session .run (
160245 "cargo" ,
161246 "llvm-cov" ,
@@ -177,3 +262,13 @@ def lint(session: nox.Session):
177262 "cargo" , "clippy" , "--allow-staged" , "--allow-dirty" , "--fix" , external = True
178263 )
179264 session .run ("cargo" , "fmt" , external = True )
265+
266+
267+ @nox .session (python = False )
268+ def install (session : nox .Session ):
269+ """Install necessary software in global env"""
270+
271+ installer = CargoInstaller (session )
272+
273+ for req in session .posargs :
274+ installer .check_install (req , session )
0 commit comments