Skip to content

Commit

Permalink
Add semantics toggle
Browse files Browse the repository at this point in the history
  • Loading branch information
charliermarsh committed May 22, 2024
1 parent e2bf5d8 commit c53ba38
Show file tree
Hide file tree
Showing 8 changed files with 225 additions and 66 deletions.
5 changes: 5 additions & 0 deletions crates/distribution-types/src/requirement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,4 +205,9 @@ impl RequirementSource {
},
}
}

/// Returns `true` if the source is editable.
pub fn is_editable(&self) -> bool {
matches!(self, Self::Path { editable: true, .. })
}
}
11 changes: 2 additions & 9 deletions crates/distribution-types/src/resolution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,9 @@ impl Resolution {
self.0.is_empty()
}

/// Return the set of [`Requirement`]s that this resolution represents, exclusive of any
/// editable requirements.
/// Return the set of [`Requirement`]s that this resolution represents.
pub fn requirements(&self) -> Vec<Requirement> {
let mut requirements: Vec<_> = self
.0
.values()
// Remove editable requirements
.filter(|dist| !dist.is_editable())
.map(Requirement::from)
.collect();
let mut requirements: Vec<_> = self.0.values().map(Requirement::from).collect();
requirements.sort_unstable_by(|a, b| a.name.cmp(&b.name));
requirements
}
Expand Down
7 changes: 6 additions & 1 deletion crates/uv-resolver/src/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ impl Manifest {

// Include direct requirements, with constraints and overrides applied.
DependencyMode::Direct => Either::Right(
self.overrides.apply(& self.requirements)
self.overrides.apply(&self.requirements)
.chain(self.constraints.requirements())
.chain(self.overrides.requirements())
.filter(move |requirement| requirement.evaluate_markers(markers, &[]))),
Expand Down Expand Up @@ -210,4 +210,9 @@ impl Manifest {
) -> impl Iterator<Item = &Requirement> {
self.constraints.apply(self.overrides.apply(requirements))
}

/// Returns the number of input requirements.
pub fn num_requirements(&self) -> usize {
self.requirements.len() + self.editables.len()
}
}
2 changes: 2 additions & 0 deletions crates/uv/src/commands/pip/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ use uv_resolver::{
use uv_types::{BuildIsolation, HashStrategy, InFlight};

use crate::commands::pip::operations;
use crate::commands::pip::operations::Modifications;
use crate::commands::reporters::ResolverReporter;
use crate::commands::{elapsed, ExitStatus};
use crate::editables::ResolvedEditables;
Expand Down Expand Up @@ -474,6 +475,7 @@ pub(crate) async fn pip_install(
&resolution,
&editables,
site_packages,
Modifications::Sufficient,
&reinstall,
&no_binary,
link_mode,
Expand Down
148 changes: 110 additions & 38 deletions crates/uv/src/commands/pip/operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,21 +198,32 @@ pub(crate) async fn resolve(
);

// Resolve the dependencies.
let resolver = Resolver::new(
manifest,
options,
&python_requirement,
Some(markers),
tags,
flat_index,
index,
hasher,
build_dispatch,
site_packages,
DistributionDatabase::new(client, build_dispatch, concurrency.downloads),
)?
.with_reporter(ResolverReporter::from(printer));
let resolution = resolver.resolve().await?;
let resolution = {
// If possible, create a bound on the progress bar.
let reporter = match options.dependency_mode {
DependencyMode::Transitive => ResolverReporter::from(printer),
DependencyMode::Direct => {
ResolverReporter::from(printer).with_length(manifest.num_requirements() as u64)
}
};

let resolver = Resolver::new(
manifest,
options,
&python_requirement,
Some(markers),
tags,
flat_index,
index,
hasher,
build_dispatch,
site_packages,
DistributionDatabase::new(client, build_dispatch, concurrency.downloads),
)?
.with_reporter(reporter);

resolver.resolve().await?
};

let s = if resolution.len() == 1 { "" } else { "s" };
writeln!(
Expand Down Expand Up @@ -240,12 +251,28 @@ pub(crate) async fn resolve(
Ok(resolution)
}

#[derive(Debug, Clone, Copy)]
pub(crate) enum Modifications {
/// Use `pip install` semantics, whereby existing installations are left as-is, unless they are
/// marked for re-installation or upgrade.
///
/// Ensures that the resulting environment is sufficient to meet the requirements, but without
/// any unnecessary changes.
Sufficient,
/// Use `pip sync` semantics, whereby any existing, extraneous installations are removed.
///
/// Ensures that the resulting environment is an exact match for the requirements, but may
/// result in more changes than necessary.
Exact,
}

/// Install a set of requirements into the current environment.
#[allow(clippy::too_many_arguments)]
pub(crate) async fn install(
resolution: &Resolution,
editables: &[ResolvedEditable],
site_packages: SitePackages,
modifications: Modifications,
reinstall: &Reinstall,
no_binary: &NoBinary,
link_mode: LinkMode,
Expand All @@ -264,7 +291,22 @@ pub(crate) async fn install(
) -> Result<(), Error> {
let start = std::time::Instant::now();

let requirements = resolution.requirements();
// Extract the requirements from the resolution, filtering out any editables that were already
// required. If a package is already installed as editable, it may appear in the resolution
// despite not being explicitly requested.
let requirements = resolution
.requirements()
.into_iter()
.filter(|requirement| {
if requirement.source.is_editable() {
!editables
.iter()
.any(|editable| requirement.name == *editable.name())
} else {
true
}
})
.collect::<Vec<_>>();

// Partition into those that should be linked from the cache (`local`), those that need to be
// downloaded (`remote`), and those that should be removed (`extraneous`).
Expand All @@ -283,18 +325,24 @@ pub(crate) async fn install(
.context("Failed to determine installation plan")?;

if dry_run {
return report_dry_run(resolution, plan, start, printer);
return report_dry_run(resolution, plan, modifications, start, printer);
}

let Plan {
cached,
remote,
reinstalls,
extraneous: _,
extraneous,
} = plan;

// If we're in `install` mode, ignore any extraneous distributions.
let extraneous = match modifications {
Modifications::Sufficient => vec![],
Modifications::Exact => extraneous,
};

// Nothing to do.
if remote.is_empty() && cached.is_empty() && reinstalls.is_empty() {
if remote.is_empty() && cached.is_empty() && reinstalls.is_empty() && extraneous.is_empty() {
let s = if resolution.len() == 1 { "" } else { "s" };
writeln!(
printer.stderr(),
Expand Down Expand Up @@ -354,9 +402,11 @@ pub(crate) async fn install(
wheels
};

// Remove any existing installations.
if !reinstalls.is_empty() {
for dist_info in &reinstalls {
// Remove any upgraded or extraneous installations.
if !extraneous.is_empty() || !reinstalls.is_empty() {
let start = std::time::Instant::now();

for dist_info in extraneous.iter().chain(reinstalls.iter()) {
match uv_installer::uninstall(dist_info).await {
Ok(summary) => {
debug!(
Expand All @@ -379,6 +429,22 @@ pub(crate) async fn install(
Err(err) => return Err(err.into()),
}
}

let s = if extraneous.len() + reinstalls.len() == 1 {
""
} else {
"s"
};
writeln!(
printer.stderr(),
"{}",
format!(
"Uninstalled {} in {}",
format!("{} package{}", extraneous.len() + reinstalls.len(), s).bold(),
elapsed(start.elapsed())
)
.dimmed()
)?;
}

// Install the resolved distributions.
Expand Down Expand Up @@ -407,8 +473,9 @@ pub(crate) async fn install(
compile_bytecode(venv, cache, printer).await?;
}

for event in reinstalls
for event in extraneous
.into_iter()
.chain(reinstalls.into_iter())
.map(|distribution| ChangeEvent {
dist: LocalDist::from(distribution),
kind: ChangeEventKind::Removed,
Expand Down Expand Up @@ -481,18 +548,25 @@ pub(crate) async fn install(
fn report_dry_run(
resolution: &Resolution,
plan: Plan,
modifications: Modifications,
start: std::time::Instant,
printer: Printer,
) -> Result<(), Error> {
let Plan {
cached,
remote,
reinstalls,
extraneous: _,
extraneous,
} = plan;

// If we're in `install` mode, ignore any extraneous distributions.
let extraneous = match modifications {
Modifications::Sufficient => vec![],
Modifications::Exact => extraneous,
};

// Nothing to do.
if remote.is_empty() && cached.is_empty() && reinstalls.is_empty() {
if remote.is_empty() && cached.is_empty() && reinstalls.is_empty() && extraneous.is_empty() {
let s = if resolution.len() == 1 { "" } else { "s" };
writeln!(
printer.stderr(),
Expand Down Expand Up @@ -536,15 +610,19 @@ fn report_dry_run(
remote
};

// Remove any existing installations.
if !reinstalls.is_empty() {
let s = if reinstalls.len() == 1 { "" } else { "s" };
// Remove any upgraded or extraneous installations.
if !extraneous.is_empty() || !reinstalls.is_empty() {
let s = if extraneous.len() + reinstalls.len() == 1 {
""
} else {
"s"
};
writeln!(
printer.stderr(),
"{}",
format!(
"Would uninstall {}",
format!("{} package{}", reinstalls.len(), s).bold(),
format!("{} package{}", extraneous.len() + reinstalls.len(), s).bold(),
)
.dimmed()
)?;
Expand All @@ -562,8 +640,9 @@ fn report_dry_run(
)?;
}

for event in reinstalls
for event in extraneous
.into_iter()
.chain(reinstalls.into_iter())
.map(|distribution| DryRunEvent {
name: distribution.name().clone(),
version: distribution.installed_version().to_string(),
Expand Down Expand Up @@ -613,8 +692,7 @@ pub(crate) fn validate(
printer: Printer,
) -> Result<(), Error> {
let site_packages = SitePackages::from_executable(venv)?;
let diagnostics = site_packages.diagnostics()?;
for diagnostic in diagnostics {
for diagnostic in site_packages.diagnostics()? {
// Only surface diagnostics that are "relevant" to the current resolution.
if resolution
.packages()
Expand All @@ -640,12 +718,6 @@ pub(crate) enum Error {
#[error(transparent)]
Uninstall(#[from] uv_installer::UninstallError),

#[error(transparent)]
Client(#[from] uv_client::Error),

#[error(transparent)]
Platform(#[from] platform_tags::PlatformError),

#[error(transparent)]
Hash(#[from] uv_types::HashStrategyError),

Expand Down
2 changes: 2 additions & 0 deletions crates/uv/src/commands/pip/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ use uv_resolver::{
use uv_types::{BuildIsolation, HashStrategy, InFlight};

use crate::commands::pip::operations;
use crate::commands::pip::operations::Modifications;
use crate::commands::reporters::ResolverReporter;
use crate::commands::ExitStatus;
use crate::editables::ResolvedEditables;
Expand Down Expand Up @@ -415,6 +416,7 @@ pub(crate) async fn pip_sync(
&resolution,
&editables,
site_packages,
Modifications::Exact,
reinstall,
&no_binary,
link_mode,
Expand Down
Loading

0 comments on commit c53ba38

Please sign in to comment.