Skip to content

Commit 3db2c84

Browse files
charliermarshkonstin
authored andcommitted
Avoid creating .venv in uv add --frozen and uv add --no-sync` (#8980)
## Summary Closes #8977.
1 parent 877cb43 commit 3db2c84

File tree

2 files changed

+77
-27
lines changed

2 files changed

+77
-27
lines changed

crates/uv/src/commands/project/add.rs

Lines changed: 67 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ use crate::commands::pip::loggers::{
4444
use crate::commands::pip::operations::Modifications;
4545
use crate::commands::project::lock::LockMode;
4646
use crate::commands::project::{
47-
init_script_python_requirement, validate_script_requires_python, ProjectError, ScriptPython,
47+
init_script_python_requirement, validate_script_requires_python, ProjectError,
48+
ProjectInterpreter, ScriptPython,
4849
};
4950
use crate::commands::reporters::{PythonDownloadReporter, ResolverReporter};
5051
use crate::commands::{diagnostics, pip, project, ExitStatus, SharedState};
@@ -214,22 +215,43 @@ pub(crate) async fn add(
214215
}
215216
}
216217

217-
// Discover or create the virtual environment.
218-
let venv = project::get_or_init_environment(
219-
project.workspace(),
220-
python.as_deref().map(PythonRequest::parse),
221-
python_preference,
222-
python_downloads,
223-
connectivity,
224-
native_tls,
225-
allow_insecure_host,
226-
no_config,
227-
cache,
228-
printer,
229-
)
230-
.await?;
218+
if frozen || no_sync {
219+
// Discover the interpreter.
220+
let interpreter = ProjectInterpreter::discover(
221+
project.workspace(),
222+
project_dir,
223+
python.as_deref().map(PythonRequest::parse),
224+
python_preference,
225+
python_downloads,
226+
connectivity,
227+
native_tls,
228+
allow_insecure_host,
229+
no_config,
230+
cache,
231+
printer,
232+
)
233+
.await?
234+
.into_interpreter();
235+
236+
Target::Project(project, Box::new(PythonTarget::Interpreter(interpreter)))
237+
} else {
238+
// Discover or create the virtual environment.
239+
let venv = project::get_or_init_environment(
240+
project.workspace(),
241+
python.as_deref().map(PythonRequest::parse),
242+
python_preference,
243+
python_downloads,
244+
connectivity,
245+
native_tls,
246+
allow_insecure_host,
247+
no_config,
248+
cache,
249+
printer,
250+
)
251+
.await?;
231252

232-
Target::Project(project, venv)
253+
Target::Project(project, Box::new(PythonTarget::Environment(venv)))
254+
}
233255
};
234256

235257
let client_builder = BaseClientBuilder::new()
@@ -576,8 +598,8 @@ pub(crate) async fn add(
576598
}
577599
};
578600

579-
let (project, venv) = match target {
580-
Target::Project(project, venv) => (project, venv),
601+
let (project, environment) = match target {
602+
Target::Project(project, environment) => (project, environment),
581603
// If `--script`, exit early. There's no reason to lock and sync.
582604
Target::Script(script, _) => {
583605
writeln!(
@@ -627,10 +649,9 @@ pub(crate) async fn add(
627649
project,
628650
&mut toml,
629651
&edits,
630-
&venv,
652+
&environment,
631653
state,
632654
locked,
633-
no_sync,
634655
&dependency_type,
635656
raw_sources,
636657
settings.as_ref(),
@@ -688,10 +709,9 @@ async fn lock_and_sync(
688709
mut project: VirtualProject,
689710
toml: &mut PyProjectTomlMut,
690711
edits: &[DependencyEdit],
691-
venv: &PythonEnvironment,
712+
environment: &PythonTarget,
692713
state: SharedState,
693714
locked: bool,
694-
no_sync: bool,
695715
dependency_type: &DependencyType,
696716
raw_sources: bool,
697717
settings: ResolverInstallerSettingsRef<'_>,
@@ -704,9 +724,9 @@ async fn lock_and_sync(
704724
printer: Printer,
705725
) -> Result<(), ProjectError> {
706726
let mode = if locked {
707-
LockMode::Locked(venv.interpreter())
727+
LockMode::Locked(environment.interpreter())
708728
} else {
709-
LockMode::Write(venv.interpreter())
729+
LockMode::Write(environment.interpreter())
710730
};
711731

712732
let mut lock = project::lock::do_safe_lock(
@@ -846,9 +866,10 @@ async fn lock_and_sync(
846866
}
847867
}
848868

849-
if no_sync {
869+
let PythonTarget::Environment(venv) = environment else {
870+
// If we're not syncing, exit early.
850871
return Ok(());
851-
}
872+
};
852873

853874
// Sync the environment.
854875
let (extras, dev) = match dependency_type {
@@ -1024,8 +1045,9 @@ fn resolve_requirement(
10241045
enum Target {
10251046
/// A PEP 723 script, with inline metadata.
10261047
Script(Pep723Script, Box<Interpreter>),
1048+
10271049
/// A project with a `pyproject.toml`.
1028-
Project(VirtualProject, PythonEnvironment),
1050+
Project(VirtualProject, Box<PythonTarget>),
10291051
}
10301052

10311053
impl Target {
@@ -1038,6 +1060,24 @@ impl Target {
10381060
}
10391061
}
10401062

1063+
/// A Python [`Interpreter`] or [`PythonEnvironment`] for a project.
1064+
#[derive(Debug)]
1065+
#[allow(clippy::large_enum_variant)]
1066+
enum PythonTarget {
1067+
Interpreter(Interpreter),
1068+
Environment(PythonEnvironment),
1069+
}
1070+
1071+
impl PythonTarget {
1072+
/// Return the [`Interpreter`] for the project.
1073+
fn interpreter(&self) -> &Interpreter {
1074+
match self {
1075+
Self::Interpreter(interpreter) => interpreter,
1076+
Self::Environment(venv) => venv.interpreter(),
1077+
}
1078+
}
1079+
}
1080+
10411081
#[derive(Debug, Clone)]
10421082
struct DependencyEdit {
10431083
dependency_type: DependencyType,

crates/uv/tests/it/edit.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3675,6 +3675,9 @@ fn add_puts_default_indentation_in_pyproject_toml_if_not_observed() -> Result<()
36753675
fn add_frozen() -> Result<()> {
36763676
let context = TestContext::new("3.12");
36773677

3678+
// Remove the virtual environment.
3679+
fs_err::remove_dir_all(&context.venv)?;
3680+
36783681
let pyproject_toml = context.temp_dir.child("pyproject.toml");
36793682
pyproject_toml.write_str(indoc! {r#"
36803683
[project]
@@ -3694,6 +3697,7 @@ fn add_frozen() -> Result<()> {
36943697
----- stdout -----
36953698
36963699
----- stderr -----
3700+
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
36973701
"###);
36983702

36993703
let pyproject_toml = context.read("pyproject.toml");
@@ -3719,6 +3723,7 @@ fn add_frozen() -> Result<()> {
37193723
});
37203724

37213725
assert!(!context.temp_dir.join("uv.lock").exists());
3726+
assert!(!context.venv.exists());
37223727

37233728
Ok(())
37243729
}
@@ -3728,6 +3733,9 @@ fn add_frozen() -> Result<()> {
37283733
fn add_no_sync() -> Result<()> {
37293734
let context = TestContext::new("3.12");
37303735

3736+
// Remove the virtual environment.
3737+
fs_err::remove_dir_all(&context.venv)?;
3738+
37313739
let pyproject_toml = context.temp_dir.child("pyproject.toml");
37323740
pyproject_toml.write_str(indoc! {r#"
37333741
[project]
@@ -3747,6 +3755,7 @@ fn add_no_sync() -> Result<()> {
37473755
----- stdout -----
37483756
37493757
----- stderr -----
3758+
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
37503759
Resolved 4 packages in [TIME]
37513760
"###);
37523761

@@ -3773,6 +3782,7 @@ fn add_no_sync() -> Result<()> {
37733782
});
37743783

37753784
assert!(context.temp_dir.join("uv.lock").exists());
3785+
assert!(!context.venv.exists());
37763786

37773787
Ok(())
37783788
}

0 commit comments

Comments
 (0)