uv_cli/
compat.rs

1use anyhow::{Result, anyhow};
2use clap::{Args, ValueEnum};
3
4use uv_warnings::warn_user;
5
6pub trait CompatArgs {
7    fn validate(&self) -> Result<()>;
8}
9
10/// Arguments for `pip-compile` compatibility.
11///
12/// These represent a subset of the `pip-compile` interface that uv supports by default.
13/// For example, users often pass `--allow-unsafe`, which is unnecessary with uv. But it's a
14/// nice user experience to warn, rather than fail, when users pass `--allow-unsafe`.
15#[derive(Args)]
16pub struct PipCompileCompatArgs {
17    #[clap(long, hide = true)]
18    allow_unsafe: bool,
19
20    #[clap(long, hide = true)]
21    no_allow_unsafe: bool,
22
23    #[clap(long, hide = true)]
24    reuse_hashes: bool,
25
26    #[clap(long, hide = true)]
27    no_reuse_hashes: bool,
28
29    #[clap(long, hide = true)]
30    resolver: Option<Resolver>,
31
32    #[clap(long, hide = true)]
33    max_rounds: Option<usize>,
34
35    #[clap(long, hide = true)]
36    cert: Option<String>,
37
38    #[clap(long, hide = true)]
39    client_cert: Option<String>,
40
41    #[clap(long, hide = true)]
42    emit_trusted_host: bool,
43
44    #[clap(long, hide = true)]
45    no_emit_trusted_host: bool,
46
47    #[clap(long, hide = true)]
48    config: Option<String>,
49
50    #[clap(long, hide = true)]
51    no_config: bool,
52
53    #[clap(long, hide = true)]
54    emit_options: bool,
55
56    #[clap(long, hide = true)]
57    no_emit_options: bool,
58
59    #[clap(long, hide = true)]
60    pip_args: Option<String>,
61}
62
63impl CompatArgs for PipCompileCompatArgs {
64    /// Validate the arguments passed for `pip-compile` compatibility.
65    ///
66    /// This method will warn when an argument is passed that has no effect but matches uv's
67    /// behavior. If an argument is passed that does _not_ match uv's behavior (e.g.,
68    /// `--no-build-isolation`), this method will return an error.
69    fn validate(&self) -> Result<()> {
70        if self.allow_unsafe {
71            warn_user!(
72                "pip-compile's `--allow-unsafe` has no effect (uv can safely pin `pip` and other packages)"
73            );
74        }
75
76        if self.no_allow_unsafe {
77            warn_user!(
78                "pip-compile's `--no-allow-unsafe` has no effect (uv can safely pin `pip` and other packages)"
79            );
80        }
81
82        if self.reuse_hashes {
83            return Err(anyhow!(
84                "pip-compile's `--reuse-hashes` is unsupported (uv doesn't reuse hashes)"
85            ));
86        }
87
88        if self.no_reuse_hashes {
89            warn_user!("pip-compile's `--no-reuse-hashes` has no effect (uv doesn't reuse hashes)");
90        }
91
92        if let Some(resolver) = self.resolver {
93            match resolver {
94                Resolver::Backtracking => {
95                    warn_user!(
96                        "pip-compile's `--resolver=backtracking` has no effect (uv always backtracks)"
97                    );
98                }
99                Resolver::Legacy => {
100                    return Err(anyhow!(
101                        "pip-compile's `--resolver=legacy` is unsupported (uv always backtracks)"
102                    ));
103                }
104            }
105        }
106
107        if self.max_rounds.is_some() {
108            return Err(anyhow!(
109                "pip-compile's `--max-rounds` is unsupported (uv always resolves until convergence)"
110            ));
111        }
112
113        if self.client_cert.is_some() {
114            return Err(anyhow!(
115                "pip-compile's `--client-cert` is unsupported (uv doesn't support dedicated client certificates)"
116            ));
117        }
118
119        if self.emit_trusted_host {
120            return Err(anyhow!(
121                "pip-compile's `--emit-trusted-host` is unsupported"
122            ));
123        }
124
125        if self.no_emit_trusted_host {
126            warn_user!(
127                "pip-compile's `--no-emit-trusted-host` has no effect (uv never emits trusted hosts)"
128            );
129        }
130
131        if self.config.is_some() {
132            return Err(anyhow!(
133                "pip-compile's `--config` is unsupported (uv does not use a configuration file)"
134            ));
135        }
136
137        if self.emit_options {
138            return Err(anyhow!(
139                "pip-compile's `--emit-options` is unsupported (uv never emits options)"
140            ));
141        }
142
143        if self.no_emit_options {
144            warn_user!("pip-compile's `--no-emit-options` has no effect (uv never emits options)");
145        }
146
147        if self.pip_args.is_some() {
148            return Err(anyhow!(
149                "pip-compile's `--pip-args` is unsupported (try passing arguments to uv directly)"
150            ));
151        }
152
153        Ok(())
154    }
155}
156
157/// Arguments for `pip list` compatibility.
158///
159/// These represent a subset of the `pip list` interface that uv supports by default.
160#[derive(Args)]
161pub struct PipListCompatArgs {
162    #[clap(long, hide = true)]
163    disable_pip_version_check: bool,
164}
165
166impl CompatArgs for PipListCompatArgs {
167    /// Validate the arguments passed for `pip list` compatibility.
168    ///
169    /// This method will warn when an argument is passed that has no effect but matches uv's
170    /// behavior. If an argument is passed that does _not_ match uv's behavior (e.g.,
171    /// `--disable-pip-version-check`), this method will return an error.
172    fn validate(&self) -> Result<()> {
173        if self.disable_pip_version_check {
174            warn_user!("pip's `--disable-pip-version-check` has no effect");
175        }
176
177        Ok(())
178    }
179}
180
181/// Arguments for `pip-sync` compatibility.
182///
183/// These represent a subset of the `pip-sync` interface that uv supports by default.
184#[derive(Args)]
185pub struct PipSyncCompatArgs {
186    #[clap(short, long, hide = true)]
187    ask: bool,
188
189    #[clap(long, hide = true)]
190    python_executable: Option<String>,
191
192    #[clap(long, hide = true)]
193    user: bool,
194
195    #[clap(long, hide = true)]
196    cert: Option<String>,
197
198    #[clap(long, hide = true)]
199    client_cert: Option<String>,
200
201    #[clap(long, hide = true)]
202    config: Option<String>,
203
204    #[clap(long, hide = true)]
205    no_config: bool,
206
207    #[clap(long, hide = true)]
208    pip_args: Option<String>,
209}
210
211impl CompatArgs for PipSyncCompatArgs {
212    /// Validate the arguments passed for `pip-sync` compatibility.
213    ///
214    /// This method will warn when an argument is passed that has no effect but matches uv's
215    /// behavior. If an argument is passed that does _not_ match uv's behavior, this method will
216    /// return an error.
217    fn validate(&self) -> Result<()> {
218        if self.ask {
219            return Err(anyhow!(
220                "pip-sync's `--ask` is unsupported (uv never asks for confirmation)"
221            ));
222        }
223
224        if self.python_executable.is_some() {
225            return Err(anyhow!(
226                "pip-sync's `--python-executable` is unsupported (to install into a separate Python environment, try setting `VIRTUAL_ENV` instead)"
227            ));
228        }
229
230        if self.user {
231            return Err(anyhow!(
232                "pip-sync's `--user` is unsupported (use a virtual environment instead)"
233            ));
234        }
235
236        if self.client_cert.is_some() {
237            return Err(anyhow!(
238                "pip-sync's `--client-cert` is unsupported (uv doesn't support dedicated client certificates)"
239            ));
240        }
241
242        if self.config.is_some() {
243            return Err(anyhow!(
244                "pip-sync's `--config` is unsupported (uv does not use a configuration file)"
245            ));
246        }
247
248        if self.pip_args.is_some() {
249            return Err(anyhow!(
250                "pip-sync's `--pip-args` is unsupported (try passing arguments to uv directly)"
251            ));
252        }
253
254        Ok(())
255    }
256}
257
258#[derive(Debug, Copy, Clone, ValueEnum)]
259enum Resolver {
260    Backtracking,
261    Legacy,
262}
263
264/// Arguments for `venv` compatibility.
265///
266/// These represent a subset of the `virtualenv` interface that uv supports by default.
267#[derive(Args)]
268pub struct VenvCompatArgs {
269    #[clap(long, hide = true)]
270    no_seed: bool,
271
272    #[clap(long, hide = true)]
273    no_pip: bool,
274
275    #[clap(long, hide = true)]
276    no_setuptools: bool,
277
278    #[clap(long, hide = true)]
279    no_wheel: bool,
280}
281
282impl CompatArgs for VenvCompatArgs {
283    /// Validate the arguments passed for `venv` compatibility.
284    ///
285    /// This method will warn when an argument is passed that has no effect but matches uv's
286    /// behavior. If an argument is passed that does _not_ match uv's behavior, this method will
287    /// return an error.
288    fn validate(&self) -> Result<()> {
289        if self.no_seed {
290            warn_user!(
291                "virtualenv's `--no-seed` has no effect (uv omits seed packages by default)"
292            );
293        }
294
295        if self.no_pip {
296            warn_user!("virtualenv's `--no-pip` has no effect (uv omits `pip` by default)");
297        }
298
299        if self.no_setuptools {
300            warn_user!(
301                "virtualenv's `--no-setuptools` has no effect (uv omits `setuptools` by default)"
302            );
303        }
304
305        if self.no_wheel {
306            warn_user!("virtualenv's `--no-wheel` has no effect (uv omits `wheel` by default)");
307        }
308
309        Ok(())
310    }
311}
312
313/// Arguments for `pip install` compatibility.
314///
315/// These represent a subset of the `pip install` interface that uv supports by default.
316#[derive(Args)]
317pub struct PipInstallCompatArgs {
318    #[clap(long, hide = true)]
319    disable_pip_version_check: bool,
320
321    #[clap(long, hide = false)]
322    user: bool,
323}
324
325impl CompatArgs for PipInstallCompatArgs {
326    /// Validate the arguments passed for `pip install` compatibility.
327    ///
328    /// This method will warn when an argument is passed that has no effect but matches uv's
329    /// behavior. If an argument is passed that does _not_ match uv's behavior, this method will
330    /// return an error.
331    fn validate(&self) -> Result<()> {
332        if self.disable_pip_version_check {
333            warn_user!("pip's `--disable-pip-version-check` has no effect");
334        }
335
336        if self.user {
337            return Err(anyhow!(
338                "pip's `--user` is unsupported (use a virtual environment instead)"
339            ));
340        }
341
342        Ok(())
343    }
344}
345
346/// Arguments for generic `pip` command compatibility.
347///
348/// These represent a subset of the `pip` interface that exists on all commands.
349#[derive(Args)]
350pub struct PipGlobalCompatArgs {
351    #[clap(long, hide = true)]
352    disable_pip_version_check: bool,
353}
354
355impl CompatArgs for PipGlobalCompatArgs {
356    /// Validate the arguments passed for `pip` compatibility.
357    ///
358    /// This method will warn when an argument is passed that has no effect but matches uv's
359    /// behavior. If an argument is passed that does _not_ match uv's behavior, this method will
360    /// return an error.
361    fn validate(&self) -> Result<()> {
362        if self.disable_pip_version_check {
363            warn_user!("pip's `--disable-pip-version-check` has no effect");
364        }
365
366        Ok(())
367    }
368}