Skip to content

Commit d8971c1

Browse files
authored
Move update_environment from run to the project namespace (#3659)
Prompted by #3657 (comment) There's still some level of discomfort here, as the `tool` module needs needs to import the `project` module to manage an environment. We should probably move most of the basic operations in the `project` module root into some sort of shared module for behind the scenes operations? Regardless, this change should simplify that future move.
1 parent d7dc184 commit d8971c1

File tree

2 files changed

+171
-173
lines changed

2 files changed

+171
-173
lines changed

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

+164-8
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,27 @@ use install_wheel_rs::linker::LinkMode;
99
use pep508_rs::MarkerEnvironment;
1010
use platform_tags::Tags;
1111
use pypi_types::Yanked;
12+
use tracing::debug;
1213
use uv_cache::Cache;
13-
use uv_client::RegistryClient;
14-
use uv_configuration::{Concurrency, Constraints, NoBinary, Overrides, Reinstall};
14+
use uv_client::{BaseClientBuilder, RegistryClient, RegistryClientBuilder};
15+
use uv_configuration::{
16+
Concurrency, ConfigSettings, Constraints, NoBinary, NoBuild, Overrides, PreviewMode, Reinstall,
17+
SetupPyStrategy,
18+
};
1519
use uv_dispatch::BuildDispatch;
1620
use uv_distribution::DistributionDatabase;
1721
use uv_fs::Simplified;
18-
use uv_installer::{Downloader, Plan, Planner, SitePackages};
22+
use uv_installer::{Downloader, Plan, Planner, SatisfiesResult, SitePackages};
1923
use uv_interpreter::{find_default_python, Interpreter, PythonEnvironment};
2024
use uv_requirements::{
21-
ExtrasSpecification, LookaheadResolver, NamedRequirementsResolver, RequirementsSpecification,
22-
SourceTreeResolver,
25+
ExtrasSpecification, LookaheadResolver, NamedRequirementsResolver, RequirementsSource,
26+
RequirementsSpecification, SourceTreeResolver,
2327
};
2428
use uv_resolver::{
25-
Exclusions, FlatIndex, InMemoryIndex, Manifest, Options, PythonRequirement, ResolutionGraph,
26-
Resolver,
29+
Exclusions, FlatIndex, InMemoryIndex, Manifest, Options, OptionsBuilder, PythonRequirement,
30+
ResolutionGraph, Resolver,
2731
};
28-
use uv_types::{HashStrategy, InFlight, InstalledPackagesProvider};
32+
use uv_types::{BuildIsolation, HashStrategy, InFlight, InstalledPackagesProvider};
2933

3034
use crate::commands::project::discovery::Project;
3135
use crate::commands::reporters::{DownloadReporter, InstallReporter, ResolverReporter};
@@ -434,3 +438,155 @@ pub(crate) async fn install(
434438

435439
Ok(())
436440
}
441+
442+
/// Update a [`PythonEnvironment`] to satisfy a set of [`RequirementsSource`]s.
443+
async fn update_environment(
444+
venv: PythonEnvironment,
445+
requirements: &[RequirementsSource],
446+
preview: PreviewMode,
447+
cache: &Cache,
448+
printer: Printer,
449+
) -> Result<PythonEnvironment> {
450+
// TODO(zanieb): Support client configuration
451+
let client_builder = BaseClientBuilder::default();
452+
453+
// Read all requirements from the provided sources.
454+
// TODO(zanieb): Consider allowing constraints and extras
455+
// TODO(zanieb): Allow specifying extras somehow
456+
let spec = RequirementsSpecification::from_sources(
457+
requirements,
458+
&[],
459+
&[],
460+
&ExtrasSpecification::None,
461+
&client_builder,
462+
preview,
463+
)
464+
.await?;
465+
466+
// Check if the current environment satisfies the requirements
467+
let site_packages = SitePackages::from_executable(&venv)?;
468+
469+
// If the requirements are already satisfied, we're done.
470+
if spec.source_trees.is_empty() {
471+
match site_packages.satisfies(&spec.requirements, &spec.editables, &spec.constraints)? {
472+
SatisfiesResult::Fresh {
473+
recursive_requirements,
474+
} => {
475+
debug!(
476+
"All requirements satisfied: {}",
477+
recursive_requirements
478+
.iter()
479+
.map(|entry| entry.requirement.to_string())
480+
.sorted()
481+
.join(" | ")
482+
);
483+
debug!(
484+
"All editables satisfied: {}",
485+
spec.editables.iter().map(ToString::to_string).join(", ")
486+
);
487+
return Ok(venv);
488+
}
489+
SatisfiesResult::Unsatisfied(requirement) => {
490+
debug!("At least one requirement is not satisfied: {requirement}");
491+
}
492+
}
493+
}
494+
495+
// Determine the tags, markers, and interpreter to use for resolution.
496+
let interpreter = venv.interpreter().clone();
497+
let tags = venv.interpreter().tags()?;
498+
let markers = venv.interpreter().markers();
499+
500+
// Initialize the registry client.
501+
// TODO(zanieb): Support client options e.g. offline, tls, etc.
502+
let client = RegistryClientBuilder::new(cache.clone())
503+
.markers(markers)
504+
.platform(venv.interpreter().platform())
505+
.build();
506+
507+
// TODO(charlie): Respect project configuration.
508+
let build_isolation = BuildIsolation::default();
509+
let config_settings = ConfigSettings::default();
510+
let flat_index = FlatIndex::default();
511+
let hasher = HashStrategy::default();
512+
let in_flight = InFlight::default();
513+
let index = InMemoryIndex::default();
514+
let index_locations = IndexLocations::default();
515+
let link_mode = LinkMode::default();
516+
let no_binary = NoBinary::default();
517+
let no_build = NoBuild::default();
518+
let setup_py = SetupPyStrategy::default();
519+
let concurrency = Concurrency::default();
520+
521+
// Create a build dispatch.
522+
let build_dispatch = BuildDispatch::new(
523+
&client,
524+
cache,
525+
&interpreter,
526+
&index_locations,
527+
&flat_index,
528+
&index,
529+
&in_flight,
530+
setup_py,
531+
&config_settings,
532+
build_isolation,
533+
link_mode,
534+
&no_build,
535+
&no_binary,
536+
concurrency,
537+
);
538+
539+
let options = OptionsBuilder::new()
540+
// TODO(zanieb): Support resolver options
541+
// .resolution_mode(resolution_mode)
542+
// .prerelease_mode(prerelease_mode)
543+
// .dependency_mode(dependency_mode)
544+
// .exclude_newer(exclude_newer)
545+
.build();
546+
547+
// Resolve the requirements.
548+
let resolution = match resolve(
549+
spec,
550+
site_packages.clone(),
551+
&hasher,
552+
&interpreter,
553+
tags,
554+
markers,
555+
&client,
556+
&flat_index,
557+
&index,
558+
&build_dispatch,
559+
options,
560+
printer,
561+
concurrency,
562+
)
563+
.await
564+
{
565+
Ok(resolution) => Resolution::from(resolution),
566+
Err(err) => return Err(err.into()),
567+
};
568+
569+
// Re-initialize the in-flight map.
570+
let in_flight = InFlight::default();
571+
572+
// Sync the environment.
573+
install(
574+
&resolution,
575+
site_packages,
576+
&no_binary,
577+
link_mode,
578+
&index_locations,
579+
&hasher,
580+
tags,
581+
&client,
582+
&in_flight,
583+
&build_dispatch,
584+
cache,
585+
&venv,
586+
printer,
587+
concurrency,
588+
)
589+
.await?;
590+
591+
Ok(venv)
592+
}

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

+7-165
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,10 @@ use tempfile::tempdir_in;
77
use tokio::process::Command;
88
use tracing::debug;
99

10-
use distribution_types::{IndexLocations, Resolution};
11-
use install_wheel_rs::linker::LinkMode;
1210
use uv_cache::Cache;
13-
use uv_client::{BaseClientBuilder, RegistryClientBuilder};
14-
use uv_configuration::{
15-
Concurrency, ConfigSettings, NoBinary, NoBuild, PreviewMode, SetupPyStrategy,
16-
};
17-
use uv_dispatch::BuildDispatch;
18-
use uv_installer::{SatisfiesResult, SitePackages};
11+
use uv_configuration::PreviewMode;
1912
use uv_interpreter::PythonEnvironment;
20-
use uv_requirements::{ExtrasSpecification, RequirementsSource, RequirementsSpecification};
21-
use uv_resolver::{FlatIndex, InMemoryIndex, OptionsBuilder};
22-
use uv_types::{BuildIsolation, HashStrategy, InFlight};
13+
use uv_requirements::RequirementsSource;
2314
use uv_warnings::warn_user;
2415

2516
use crate::commands::project::discovery::Project;
@@ -73,7 +64,10 @@ pub(crate) async fn run(
7364
let venv = project::init(&project, cache, printer)?;
7465

7566
// Install the project requirements.
76-
Some(update_environment(venv, &project.requirements(), preview, cache, printer).await?)
67+
Some(
68+
project::update_environment(venv, &project.requirements(), preview, cache, printer)
69+
.await?,
70+
)
7771
};
7872

7973
// If necessary, create an environment for the ephemeral requirements.
@@ -111,7 +105,7 @@ pub(crate) async fn run(
111105
)?;
112106

113107
// Install the ephemeral requirements.
114-
Some(update_environment(venv, &requirements, preview, cache, printer).await?)
108+
Some(project::update_environment(venv, &requirements, preview, cache, printer).await?)
115109
};
116110

117111
// Construct the command
@@ -183,155 +177,3 @@ pub(crate) async fn run(
183177
Ok(ExitStatus::Failure)
184178
}
185179
}
186-
187-
/// Update a [`PythonEnvironment`] to satisfy a set of [`RequirementsSource`]s.
188-
async fn update_environment(
189-
venv: PythonEnvironment,
190-
requirements: &[RequirementsSource],
191-
preview: PreviewMode,
192-
cache: &Cache,
193-
printer: Printer,
194-
) -> Result<PythonEnvironment> {
195-
// TODO(zanieb): Support client configuration
196-
let client_builder = BaseClientBuilder::default();
197-
198-
// Read all requirements from the provided sources.
199-
// TODO(zanieb): Consider allowing constraints and extras
200-
// TODO(zanieb): Allow specifying extras somehow
201-
let spec = RequirementsSpecification::from_sources(
202-
requirements,
203-
&[],
204-
&[],
205-
&ExtrasSpecification::None,
206-
&client_builder,
207-
preview,
208-
)
209-
.await?;
210-
211-
// Check if the current environment satisfies the requirements
212-
let site_packages = SitePackages::from_executable(&venv)?;
213-
214-
// If the requirements are already satisfied, we're done.
215-
if spec.source_trees.is_empty() {
216-
match site_packages.satisfies(&spec.requirements, &spec.editables, &spec.constraints)? {
217-
SatisfiesResult::Fresh {
218-
recursive_requirements,
219-
} => {
220-
debug!(
221-
"All requirements satisfied: {}",
222-
recursive_requirements
223-
.iter()
224-
.map(|entry| entry.requirement.to_string())
225-
.sorted()
226-
.join(" | ")
227-
);
228-
debug!(
229-
"All editables satisfied: {}",
230-
spec.editables.iter().map(ToString::to_string).join(", ")
231-
);
232-
return Ok(venv);
233-
}
234-
SatisfiesResult::Unsatisfied(requirement) => {
235-
debug!("At least one requirement is not satisfied: {requirement}");
236-
}
237-
}
238-
}
239-
240-
// Determine the tags, markers, and interpreter to use for resolution.
241-
let interpreter = venv.interpreter().clone();
242-
let tags = venv.interpreter().tags()?;
243-
let markers = venv.interpreter().markers();
244-
245-
// Initialize the registry client.
246-
// TODO(zanieb): Support client options e.g. offline, tls, etc.
247-
let client = RegistryClientBuilder::new(cache.clone())
248-
.markers(markers)
249-
.platform(venv.interpreter().platform())
250-
.build();
251-
252-
// TODO(charlie): Respect project configuration.
253-
let build_isolation = BuildIsolation::default();
254-
let config_settings = ConfigSettings::default();
255-
let flat_index = FlatIndex::default();
256-
let hasher = HashStrategy::default();
257-
let in_flight = InFlight::default();
258-
let index = InMemoryIndex::default();
259-
let index_locations = IndexLocations::default();
260-
let link_mode = LinkMode::default();
261-
let no_binary = NoBinary::default();
262-
let no_build = NoBuild::default();
263-
let setup_py = SetupPyStrategy::default();
264-
let concurrency = Concurrency::default();
265-
266-
// Create a build dispatch.
267-
let build_dispatch = BuildDispatch::new(
268-
&client,
269-
cache,
270-
&interpreter,
271-
&index_locations,
272-
&flat_index,
273-
&index,
274-
&in_flight,
275-
setup_py,
276-
&config_settings,
277-
build_isolation,
278-
link_mode,
279-
&no_build,
280-
&no_binary,
281-
concurrency,
282-
);
283-
284-
let options = OptionsBuilder::new()
285-
// TODO(zanieb): Support resolver options
286-
// .resolution_mode(resolution_mode)
287-
// .prerelease_mode(prerelease_mode)
288-
// .dependency_mode(dependency_mode)
289-
// .exclude_newer(exclude_newer)
290-
.build();
291-
292-
// Resolve the requirements.
293-
let resolution = match project::resolve(
294-
spec,
295-
site_packages.clone(),
296-
&hasher,
297-
&interpreter,
298-
tags,
299-
markers,
300-
&client,
301-
&flat_index,
302-
&index,
303-
&build_dispatch,
304-
options,
305-
printer,
306-
concurrency,
307-
)
308-
.await
309-
{
310-
Ok(resolution) => Resolution::from(resolution),
311-
Err(err) => return Err(err.into()),
312-
};
313-
314-
// Re-initialize the in-flight map.
315-
let in_flight = InFlight::default();
316-
317-
// Sync the environment.
318-
project::install(
319-
&resolution,
320-
site_packages,
321-
&no_binary,
322-
link_mode,
323-
&index_locations,
324-
&hasher,
325-
tags,
326-
&client,
327-
&in_flight,
328-
&build_dispatch,
329-
cache,
330-
&venv,
331-
printer,
332-
concurrency,
333-
)
334-
.await?;
335-
336-
Ok(venv)
337-
}

0 commit comments

Comments
 (0)