Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion crates/uv-dispatch/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use uv_distribution_types::{
PackageConfigSettings, Requirement, Resolution, SourceDist, VersionOrUrlRef,
};
use uv_git::GitResolver;
use uv_installer::{Installer, Plan, Planner, Preparer, SitePackages};
use uv_installer::{Installer, Plan, Planner, Preparer, SitePackages, SyncModel};
use uv_pypi_types::Conflicts;
use uv_python::{Interpreter, PythonEnvironment};
use uv_resolver::{
Expand Down Expand Up @@ -319,6 +319,7 @@ impl BuildContext for BuildDispatch<'_> {
self.build_options,
self.hasher,
self.index_locations,
SyncModel::Stateless,
self.config_settings,
self.config_settings_package,
self.extra_build_requires(),
Expand Down
2 changes: 1 addition & 1 deletion crates/uv-installer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ pub use compile::{CompileError, compile_tree};
pub use installer::{Installer, Reporter as InstallReporter};
pub use plan::{Plan, Planner};
pub use preparer::{Error as PrepareError, Preparer, Reporter as PrepareReporter};
pub use site_packages::{SatisfiesResult, SitePackages, SitePackagesDiagnostic};
pub use site_packages::{SatisfiesResult, SitePackages, SitePackagesDiagnostic, SyncModel};
pub use uninstall::{UninstallError, uninstall};

mod compile;
Expand Down
3 changes: 3 additions & 0 deletions crates/uv-installer/src/plan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use uv_types::HashStrategy;

use crate::SitePackages;
use crate::satisfies::RequirementSatisfaction;
use crate::site_packages::SyncModel;

/// A planner to generate an [`Plan`] based on a set of requirements.
#[derive(Debug)]
Expand Down Expand Up @@ -56,6 +57,7 @@ impl<'a> Planner<'a> {
build_options: &BuildOptions,
hasher: &HashStrategy,
index_locations: &IndexLocations,
model: SyncModel,
config_settings: &ConfigSettings,
config_settings_package: &PackageConfigSettings,
extra_build_requires: &ExtraBuildRequires,
Expand Down Expand Up @@ -125,6 +127,7 @@ impl<'a> Planner<'a> {
dist.name(),
installed,
&source,
model,
config_settings,
config_settings_package,
extra_build_requires,
Expand Down
22 changes: 22 additions & 0 deletions crates/uv-installer/src/satisfies.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use same_file::is_same_file;
use tracing::{debug, trace};
use url::Url;

use crate::site_packages::SyncModel;
use uv_cache_info::CacheInfo;
use uv_cache_key::{CanonicalUrl, RepositoryUrl};
use uv_distribution_types::{
Expand Down Expand Up @@ -32,6 +33,7 @@ impl RequirementSatisfaction {
name: &PackageName,
distribution: &InstalledDist,
source: &RequirementSource,
model: SyncModel,
config_settings: &ConfigSettings,
config_settings_package: &PackageConfigSettings,
extra_build_requires: &ExtraBuildRequires,
Expand All @@ -55,6 +57,7 @@ impl RequirementSatisfaction {
);
dist_build_info != &build_info
}) {
// If the requirement came from a registry,
debug!("Build info mismatch for {name}: {distribution:?}");
return Self::OutOfDate;
}
Expand All @@ -63,6 +66,25 @@ impl RequirementSatisfaction {
match source {
// If the requirement comes from a registry, check by name.
RequirementSource::Registry { specifier, .. } => {
// If the installed distribution is _not_ from a registry, reject it if and only if
// we're in a stateless install.
//
// For example: the `uv pip` CLI is stateful, in that it "respects"
// already-installed packages in the virtual environment. So if you run `uv pip
// install ./path/to/idna`, and then `uv pip install anyio` (which depends on
// `idna`), we'll "accept" the already-installed `idna` even though it is implicitly
// being "required" as a registry package.
//
// The `uv sync` CLI is stateless, in that all requirements must be defined
// declaratively ahead-of-time. So if you `uv sync` to install `./path/to/idna` and
// later `uv sync` to install `anyio`, we'll know (during that second sync) if the
// already-installed `idna` should come from the registry or not.
if model == SyncModel::Stateless {
if !matches!(distribution, InstalledDist::Registry { .. }) {
debug!("Distribution type mismatch for {name}: {distribution:?}");
return Self::Mismatch;
}
}
if specifier.contains(distribution.version()) {
return Self::Satisfied;
}
Expand Down
18 changes: 18 additions & 0 deletions crates/uv-installer/src/site_packages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ impl SitePackages {
constraints: &[NameRequirementSpecification],
overrides: &[UnresolvedRequirementSpecification],
markers: &ResolverMarkerEnvironment,
model: SyncModel,
config_settings: &ConfigSettings,
config_settings_package: &PackageConfigSettings,
extra_build_requires: &ExtraBuildRequires,
Expand Down Expand Up @@ -385,6 +386,7 @@ impl SitePackages {
constraints.iter().map(|constraint| &constraint.requirement),
overrides.iter().map(Cow::as_ref),
markers,
model,
config_settings,
config_settings_package,
extra_build_requires,
Expand All @@ -399,6 +401,7 @@ impl SitePackages {
constraints: impl Iterator<Item = &'a Requirement>,
overrides: impl Iterator<Item = &'a Requirement>,
markers: &ResolverMarkerEnvironment,
model: SyncModel,
config_settings: &ConfigSettings,
config_settings_package: &PackageConfigSettings,
extra_build_requires: &ExtraBuildRequires,
Expand Down Expand Up @@ -460,6 +463,7 @@ impl SitePackages {
name,
distribution,
&requirement.source,
model,
config_settings,
config_settings_package,
extra_build_requires,
Expand All @@ -481,6 +485,7 @@ impl SitePackages {
name,
distribution,
&constraint.source,
model,
config_settings,
config_settings_package,
extra_build_requires,
Expand Down Expand Up @@ -536,6 +541,19 @@ impl SitePackages {
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SyncModel {
/// A stateful sync, as in the `uv pip` CLI, whereby packages that are already installed in
/// the environment may be reused if they implicitly match the requirements. For example, if
/// the user installs `./path/to/idna`, then runs `uv pip install anyio` (which depends on
/// `idna`), the existing `idna` installation will be reused if it satisfies the requirements,
/// even though it is implicitly being requested from the registry.
Stateful,
/// A stateless sync, as in the `uv sync` CLI, whereby the sources of all packages are defined
/// declaratively upfront.
Stateless,
}

/// We check if all requirements are already satisfied, recursing through the requirements tree.
#[derive(Debug)]
pub enum SatisfiesResult {
Expand Down
4 changes: 3 additions & 1 deletion crates/uv/src/commands/pip/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use uv_distribution_types::{
};
use uv_fs::Simplified;
use uv_install_wheel::LinkMode;
use uv_installer::{SatisfiesResult, SitePackages};
use uv_installer::{SatisfiesResult, SitePackages, SyncModel};
use uv_normalize::{DefaultExtras, DefaultGroups};
use uv_pypi_types::Conflicts;
use uv_python::{
Expand Down Expand Up @@ -290,6 +290,7 @@ pub(crate) async fn pip_install(
&constraints,
&overrides,
&marker_env,
SyncModel::Stateful,
config_settings,
config_settings_package,
&extra_build_requires,
Expand Down Expand Up @@ -601,6 +602,7 @@ pub(crate) async fn pip_install(
match operations::install(
&resolution,
site_packages,
SyncModel::Stateful,
modifications,
&reinstall,
&build_options,
Expand Down
4 changes: 3 additions & 1 deletion crates/uv/src/commands/pip/operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use uv_distribution_types::{
use uv_distribution_types::{DistributionMetadata, InstalledMetadata, Name, Resolution};
use uv_fs::Simplified;
use uv_install_wheel::LinkMode;
use uv_installer::{Plan, Planner, Preparer, SitePackages};
use uv_installer::{Plan, Planner, Preparer, SitePackages, SyncModel};
use uv_normalize::PackageName;
use uv_pep508::{MarkerEnvironment, RequirementOrigin};
use uv_platform_tags::Tags;
Expand Down Expand Up @@ -433,6 +433,7 @@ impl Changelog {
pub(crate) async fn install(
resolution: &Resolution,
site_packages: SitePackages,
model: SyncModel,
modifications: Modifications,
reinstall: &Reinstall,
build_options: &BuildOptions,
Expand Down Expand Up @@ -463,6 +464,7 @@ pub(crate) async fn install(
build_options,
hasher,
build_dispatch.locations(),
model,
build_dispatch.config_settings(),
build_dispatch.config_settings_package(),
build_dispatch.extra_build_requires(),
Expand Down
3 changes: 2 additions & 1 deletion crates/uv/src/commands/pip/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use uv_distribution_types::{
};
use uv_fs::Simplified;
use uv_install_wheel::LinkMode;
use uv_installer::SitePackages;
use uv_installer::{SitePackages, SyncModel};
use uv_normalize::{DefaultExtras, DefaultGroups};
use uv_pypi_types::Conflicts;
use uv_python::{
Expand Down Expand Up @@ -531,6 +531,7 @@ pub(crate) async fn pip_sync(
match operations::install(
&resolution,
site_packages,
SyncModel::Stateful,
Modifications::Exact,
&reinstall,
&build_options,
Expand Down
5 changes: 4 additions & 1 deletion crates/uv/src/commands/project/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use uv_distribution_types::{
};
use uv_fs::{CWD, LockedFile, Simplified};
use uv_git::ResolvedRepositoryReference;
use uv_installer::{SatisfiesResult, SitePackages};
use uv_installer::{SatisfiesResult, SitePackages, SyncModel};
use uv_normalize::{DEV_DEPENDENCIES, DefaultGroups, ExtraName, GroupName, PackageName};
use uv_pep440::{TildeVersionSpecifier, Version, VersionSpecifiers};
use uv_pep508::MarkerTreeContents;
Expand Down Expand Up @@ -2158,6 +2158,7 @@ pub(crate) async fn sync_environment(
pip::operations::install(
resolution,
site_packages,
SyncModel::Stateless,
modifications,
reinstall,
build_options,
Expand Down Expand Up @@ -2281,6 +2282,7 @@ pub(crate) async fn update_environment(
&constraints,
&overrides,
&marker_env,
SyncModel::Stateless,
config_setting,
config_settings_package,
&extra_build_requires,
Expand Down Expand Up @@ -2428,6 +2430,7 @@ pub(crate) async fn update_environment(
let changelog = pip::operations::install(
&resolution,
site_packages,
SyncModel::Stateless,
modifications,
reinstall,
build_options,
Expand Down
3 changes: 2 additions & 1 deletion crates/uv/src/commands/project/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use uv_distribution::LoweredExtraBuildDependencies;
use uv_distribution_types::Requirement;
use uv_fs::which::is_executable;
use uv_fs::{PythonExt, Simplified, create_symlink};
use uv_installer::{SatisfiesResult, SitePackages};
use uv_installer::{SatisfiesResult, SitePackages, SyncModel};
use uv_normalize::{DefaultExtras, DefaultGroups, PackageName};
use uv_python::{
EnvironmentPreference, Interpreter, PyVenvConfiguration, PythonDownloads, PythonEnvironment,
Expand Down Expand Up @@ -1363,6 +1363,7 @@ fn can_skip_ephemeral(
&spec.constraints,
&spec.overrides,
&interpreter.resolver_marker_environment(),
SyncModel::Stateless,
config_setting,
config_settings_package,
&extra_build_requires,
Expand Down
3 changes: 2 additions & 1 deletion crates/uv/src/commands/project/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use uv_distribution_types::{
DirectorySourceDist, Dist, Index, Requirement, Resolution, ResolvedDist, SourceDist,
};
use uv_fs::{PortablePathBuf, Simplified};
use uv_installer::SitePackages;
use uv_installer::{SitePackages, SyncModel};
use uv_normalize::{DefaultExtras, DefaultGroups, PackageName};
use uv_pep508::{MarkerTree, VersionOrUrl};
use uv_pypi_types::{ParsedArchiveUrl, ParsedGitUrl, ParsedUrl};
Expand Down Expand Up @@ -790,6 +790,7 @@ pub(super) async fn do_sync(
operations::install(
&resolution,
site_packages,
SyncModel::Stateless,
modifications,
reinstall,
build_options,
Expand Down
3 changes: 2 additions & 1 deletion crates/uv/src/commands/tool/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use uv_distribution_types::{
UnresolvedRequirement, UnresolvedRequirementSpecification,
};
use uv_fs::Simplified;
use uv_installer::{SatisfiesResult, SitePackages};
use uv_installer::{SatisfiesResult, SitePackages, SyncModel};
use uv_normalize::PackageName;
use uv_pep440::{VersionSpecifier, VersionSpecifiers};
use uv_pep508::MarkerTree;
Expand Down Expand Up @@ -972,6 +972,7 @@ async fn get_or_create_environment(
constraints.iter(),
overrides.iter(),
&interpreter.resolver_marker_environment(),
SyncModel::Stateless,
config_setting,
config_settings_package,
&extra_build_requires,
Expand Down
Loading
Loading