Files
uv/crates/uv-dispatch/src/lib.rs
T

344 lines
11 KiB
Rust
Raw Normal View History

2024-02-15 11:19:46 -06:00
//! Avoid cyclic crate dependencies between [resolver][`uv_resolver`],
//! [installer][`uv_installer`] and [build][`uv_build`] through [`BuildDispatch`]
//! implementing [`BuildContext`].
use std::ffi::{OsStr, OsString};
use std::path::Path;
use anyhow::{anyhow, Context, Result};
use futures::FutureExt;
2023-11-24 18:47:58 +01:00
use itertools::Itertools;
use rustc_hash::FxHashMap;
use tracing::{debug, instrument};
use distribution_types::{CachedDist, IndexLocations, Name, Resolution, SourceDist};
use pypi_types::Requirement;
2024-02-15 11:19:46 -06:00
use uv_build::{SourceBuild, SourceBuildContext};
use uv_cache::Cache;
use uv_client::RegistryClient;
use uv_configuration::{
BuildKind, BuildOptions, ConfigSettings, IndexStrategy, Reinstall, SetupPyStrategy,
};
use uv_configuration::{Concurrency, PreviewMode};
2024-05-10 12:43:08 -04:00
use uv_distribution::DistributionDatabase;
use uv_git::GitResolver;
2024-06-18 17:00:10 -04:00
use uv_installer::{Installer, Plan, Planner, Preparer, SitePackages};
2024-07-03 08:44:29 -04:00
use uv_python::{Interpreter, PythonEnvironment};
use uv_resolver::{
ExcludeNewer, FlatIndex, InMemoryIndex, Manifest, OptionsBuilder, PythonRequirement, Resolver,
ResolverMarkers,
};
use uv_types::{BuildContext, BuildIsolation, EmptyInstalledPackages, HashStrategy, InFlight};
/// The main implementation of [`BuildContext`], used by the CLI, see [`BuildContext`]
/// documentation.
2023-12-18 11:43:03 -05:00
pub struct BuildDispatch<'a> {
client: &'a RegistryClient,
cache: &'a Cache,
interpreter: &'a Interpreter,
index_locations: &'a IndexLocations,
index_strategy: IndexStrategy,
2024-01-15 11:02:02 -05:00
flat_index: &'a FlatIndex,
index: &'a InMemoryIndex,
git: &'a GitResolver,
in_flight: &'a InFlight,
2024-01-09 20:27:06 -05:00
setup_py: SetupPyStrategy,
build_isolation: BuildIsolation<'a>,
link_mode: install_wheel_rs::linker::LinkMode,
build_options: &'a BuildOptions,
config_settings: &'a ConfigSettings,
exclude_newer: Option<ExcludeNewer>,
source_build_context: SourceBuildContext,
build_extra_env_vars: FxHashMap<OsString, OsString>,
2024-05-10 12:43:08 -04:00
concurrency: Concurrency,
preview_mode: PreviewMode,
}
2023-12-18 11:43:03 -05:00
impl<'a> BuildDispatch<'a> {
pub fn new(
2023-12-18 11:43:03 -05:00
client: &'a RegistryClient,
cache: &'a Cache,
interpreter: &'a Interpreter,
index_locations: &'a IndexLocations,
2024-01-15 11:02:02 -05:00
flat_index: &'a FlatIndex,
index: &'a InMemoryIndex,
git: &'a GitResolver,
in_flight: &'a InFlight,
index_strategy: IndexStrategy,
2024-01-09 20:27:06 -05:00
setup_py: SetupPyStrategy,
config_settings: &'a ConfigSettings,
build_isolation: BuildIsolation<'a>,
link_mode: install_wheel_rs::linker::LinkMode,
build_options: &'a BuildOptions,
exclude_newer: Option<ExcludeNewer>,
2024-05-10 12:43:08 -04:00
concurrency: Concurrency,
preview_mode: PreviewMode,
) -> Self {
Self {
client,
cache,
interpreter,
index_locations,
2024-01-15 11:02:02 -05:00
flat_index,
index,
git,
in_flight,
index_strategy,
2024-01-09 20:27:06 -05:00
setup_py,
config_settings,
build_isolation,
link_mode,
build_options,
exclude_newer,
2024-05-10 12:43:08 -04:00
concurrency,
source_build_context: SourceBuildContext::default(),
build_extra_env_vars: FxHashMap::default(),
preview_mode,
}
}
/// Set the environment variables to be used when building a source distribution.
#[must_use]
pub fn with_build_extra_env_vars<I, K, V>(mut self, sdist_build_env_variables: I) -> Self
where
I: IntoIterator<Item = (K, V)>,
K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
self.build_extra_env_vars = sdist_build_env_variables
.into_iter()
.map(|(key, value)| (key.as_ref().to_owned(), value.as_ref().to_owned()))
.collect();
self
}
}
2023-12-18 11:43:03 -05:00
impl<'a> BuildContext for BuildDispatch<'a> {
type SourceDistBuilder = SourceBuild;
fn cache(&self) -> &Cache {
2023-12-18 11:43:03 -05:00
self.cache
}
fn git(&self) -> &GitResolver {
self.git
}
fn interpreter(&self) -> &Interpreter {
2023-12-18 11:43:03 -05:00
self.interpreter
}
fn build_options(&self) -> &BuildOptions {
self.build_options
}
fn index_locations(&self) -> &IndexLocations {
self.index_locations
}
2023-12-29 16:49:12 +01:00
async fn resolve<'data>(&'data self, requirements: &'data [Requirement]) -> Result<Resolution> {
let python_requirement = PythonRequirement::from_interpreter(self.interpreter);
2023-12-29 16:49:12 +01:00
let markers = self.interpreter.markers();
let tags = self.interpreter.tags()?;
let resolver = Resolver::new(
Manifest::simple(requirements.to_vec()),
OptionsBuilder::new()
.exclude_newer(self.exclude_newer)
.index_strategy(self.index_strategy)
.build(),
&python_requirement,
ResolverMarkers::SpecificEnvironment(markers.clone()),
2024-06-10 05:38:21 -07:00
Some(tags),
2024-01-15 11:02:02 -05:00
self.flat_index,
self.index,
&HashStrategy::None,
2023-12-29 16:49:12 +01:00
self,
2024-05-17 11:47:30 -04:00
EmptyInstalledPackages,
DistributionDatabase::new(
self.client,
self,
self.concurrency.downloads,
self.preview_mode,
),
)?;
2023-12-29 16:49:12 +01:00
let graph = resolver.resolve().await.with_context(|| {
format!(
"No solution found when resolving: {}",
requirements.iter().map(ToString::to_string).join(", "),
)
})?;
Ok(Resolution::from(graph))
}
2023-11-29 11:34:18 +01:00
#[instrument(
skip(self, resolution, venv),
2023-11-29 11:34:18 +01:00
fields(
resolution = resolution.distributions().map(ToString::to_string).join(", "),
2023-11-29 11:34:18 +01:00
venv = ?venv.root()
)
)]
2024-04-10 15:26:18 +02:00
async fn install<'data>(
2023-12-18 11:43:03 -05:00
&'data self,
resolution: &'data Resolution,
venv: &'data PythonEnvironment,
2024-05-22 15:43:20 -04:00
) -> Result<Vec<CachedDist>> {
2024-04-10 15:26:18 +02:00
debug!(
"Installing in {} in {}",
resolution
.distributions()
.map(ToString::to_string)
.join(", "),
venv.root().display(),
);
2024-04-10 15:26:18 +02:00
// Determine the current environment markers.
let tags = self.interpreter.tags()?;
2024-04-10 15:26:18 +02:00
// Determine the set of installed packages.
let site_packages = SitePackages::from_environment(venv)?;
2023-10-26 11:54:47 -07:00
let requirements = resolution.requirements().collect::<Vec<_>>();
2024-04-10 15:26:18 +02:00
let Plan {
cached,
remote,
reinstalls,
extraneous: _,
} = Planner::new(&requirements).build(
2024-04-10 15:26:18 +02:00
site_packages,
&Reinstall::default(),
&BuildOptions::default(),
&HashStrategy::default(),
2024-04-10 15:26:18 +02:00
self.index_locations,
self.cache(),
venv,
tags,
)?;
2024-04-10 15:26:18 +02:00
// Nothing to do.
if remote.is_empty() && cached.is_empty() && reinstalls.is_empty() {
debug!("No build requirements to install for build");
2024-05-22 15:43:20 -04:00
return Ok(vec![]);
2024-04-10 15:26:18 +02:00
}
2023-10-26 11:54:47 -07:00
2024-04-10 15:26:18 +02:00
// Resolve any registry-based requirements.
let remote = remote
.iter()
.map(|dist| {
resolution
.get_remote(&dist.name)
.cloned()
.expect("Resolution should contain all packages")
})
.collect::<Vec<_>>();
2023-10-26 11:54:47 -07:00
2024-04-10 15:26:18 +02:00
// Download any missing distributions.
let wheels = if remote.is_empty() {
vec![]
} else {
// TODO(konstin): Check that there is no endless recursion.
2024-06-18 17:00:10 -04:00
let preparer = Preparer::new(
2024-05-10 12:43:08 -04:00
self.cache,
tags,
&HashStrategy::None,
DistributionDatabase::new(
self.client,
self,
self.concurrency.downloads,
self.preview_mode,
),
2024-05-10 12:43:08 -04:00
);
2024-04-10 15:26:18 +02:00
debug!(
"Downloading and building requirement{} for build: {}",
if remote.len() == 1 { "" } else { "s" },
remote.iter().map(ToString::to_string).join(", ")
);
2024-06-18 17:00:10 -04:00
preparer
.prepare(remote, self.in_flight)
2024-04-10 15:26:18 +02:00
.await
2024-06-18 17:00:10 -04:00
.context("Failed to prepare distributions")?
2024-04-10 15:26:18 +02:00
};
2024-04-10 15:26:18 +02:00
// Remove any unnecessary packages.
if !reinstalls.is_empty() {
for dist_info in &reinstalls {
let summary = uv_installer::uninstall(dist_info)
.await
.context("Failed to uninstall build dependencies")?;
2023-10-26 11:54:47 -07:00
debug!(
2024-04-10 15:26:18 +02:00
"Uninstalled {} ({} file{}, {} director{})",
dist_info.name(),
summary.file_count,
if summary.file_count == 1 { "" } else { "s" },
summary.dir_count,
if summary.dir_count == 1 { "y" } else { "ies" },
2023-10-26 11:54:47 -07:00
);
}
2024-04-10 15:26:18 +02:00
}
2023-10-26 11:54:47 -07:00
2024-04-10 15:26:18 +02:00
// Install the resolved distributions.
let mut wheels = wheels.into_iter().chain(cached).collect::<Vec<_>>();
2024-04-10 15:26:18 +02:00
if !wheels.is_empty() {
debug!(
"Installing build requirement{}: {}",
if wheels.len() == 1 { "" } else { "s" },
wheels.iter().map(ToString::to_string).join(", ")
);
wheels = Installer::new(venv)
.with_link_mode(self.link_mode)
.install(wheels)
.await
2024-04-10 15:26:18 +02:00
.context("Failed to install build dependencies")?;
2023-12-29 16:49:12 +01:00
}
2024-04-10 15:26:18 +02:00
2024-05-22 15:43:20 -04:00
Ok(wheels)
}
#[instrument(skip_all, fields(version_id = version_id, subdirectory = ?subdirectory))]
async fn setup_build<'data>(
2023-12-18 11:43:03 -05:00
&'data self,
source: &'data Path,
subdirectory: Option<&'data Path>,
version_id: &'data str,
dist: Option<&'data SourceDist>,
build_kind: BuildKind,
) -> Result<SourceBuild> {
// Note we can only prevent builds by name for packages with names
// unless all builds are disabled.
if self
.build_options
.no_build_requirement(dist.map(distribution_types::Name::name))
// We always allow editable builds
&& !matches!(build_kind, BuildKind::Editable)
{
if let Some(dist) = dist {
return Err(anyhow!(
"Building source distributions for {} is disabled",
dist.name()
));
}
return Err(anyhow!("Building source distributions is disabled"));
2023-12-29 16:49:12 +01:00
}
let builder = SourceBuild::setup(
source,
subdirectory,
self.interpreter,
self,
self.source_build_context.clone(),
version_id.to_string(),
self.setup_py,
self.config_settings.clone(),
self.build_isolation,
build_kind,
self.build_extra_env_vars.clone(),
2024-05-10 12:43:08 -04:00
self.concurrency.builds,
)
.boxed_local()
.await?;
Ok(builder)
}
}