Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --dry-run flag to uv pip install #1436

Merged
merged 23 commits into from
Mar 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
ea54bee
feat(pip-install): add `--dry-run` flag
JacobCoffee Feb 16, 2024
9588802
feat(pip-install): properly print versions
JacobCoffee Feb 16, 2024
ff02255
test(pip-install): attempt to add (probably shitty) test
JacobCoffee Feb 16, 2024
5cd1473
feat(pip-install): move dry run into `install` function
JacobCoffee Feb 20, 2024
6892b17
feat(pip-install): add tests, match prod formatting, add DryRunEvent
JacobCoffee Feb 25, 2024
fee46b3
Fix duplicate `==` signs in display
zanieb Mar 4, 2024
7eb0f93
Append tests to `main`
zanieb Mar 4, 2024
44f5e8e
Merge branch 'main' into 1244-pip-install-dry-run-flag
zanieb Mar 4, 2024
0f5a423
Remove unncessary `to_string()`
zanieb Mar 4, 2024
d5ef665
Clean up test cases
zanieb Mar 4, 2024
1b4ca0b
Fix display of added URLs
zanieb Mar 6, 2024
861f805
Add uninstall URL dependency test case
zanieb Mar 6, 2024
91ad98b
Use `installed_version` display for uninstalls
zanieb Mar 6, 2024
3f75e84
Display message when no changes would be mad
zanieb Mar 6, 2024
33e723a
Include tests from `main`
zanieb Mar 6, 2024
a12d9b5
Merge branch 'main' into 1244-pip-install-dry-run-flag
zanieb Mar 6, 2024
22dcb9e
Merge branch 'main' into 1244-pip-install-dry-run-flag
zanieb Mar 6, 2024
415a4f4
Ignore lint
zanieb Mar 6, 2024
73c0984
Fix extraneous comma
zanieb Mar 6, 2024
85b06e9
Copy tests from `main`
zanieb Mar 7, 2024
89f723e
Merge branch 'main' into 1244-pip-install-dry-run-flag
zanieb Mar 7, 2024
4dd46df
Copy tests from `main`
zanieb Mar 12, 2024
e6c8442
Merge branch 'main' into 1244-pip-install-dry-run-flag
zanieb Mar 12, 2024
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
11 changes: 9 additions & 2 deletions crates/uv/src/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use std::fmt::Write;
use std::process::ExitCode;
use std::time::Duration;
use std::{fmt::Display, fmt::Write, process::ExitCode};

use anyhow::Context;
use owo_colors::OwoColorize;
Expand All @@ -19,6 +18,7 @@ use uv_cache::Cache;
use uv_fs::Simplified;
use uv_installer::compile_tree;
use uv_interpreter::PythonEnvironment;
use uv_normalize::PackageName;
pub(crate) use venv::venv;
pub(crate) use version::version;

Expand Down Expand Up @@ -89,6 +89,13 @@ pub(super) struct ChangeEvent<T: InstalledMetadata> {
kind: ChangeEventKind,
}

#[derive(Debug)]
pub(super) struct DryRunEvent<T: Display> {
name: PackageName,
version: T,
kind: ChangeEventKind,
}

#[derive(Debug, Clone, Copy, clap::ValueEnum)]
pub(crate) enum VersionFormat {
Text,
Expand Down
164 changes: 153 additions & 11 deletions crates/uv/src/commands/pip_install.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::collections::HashSet;
use std::fmt::Write;
use std::path::Path;
use std::time::Instant;

use anstream::eprint;
use anyhow::{anyhow, Context, Result};
Expand All @@ -11,7 +12,8 @@ use tempfile::tempdir_in;
use tracing::debug;

use distribution_types::{
IndexLocations, InstalledMetadata, LocalDist, LocalEditable, Name, Resolution,
DistributionMetadata, IndexLocations, InstalledMetadata, LocalDist, LocalEditable, Name,
Resolution,
};
use install_wheel_rs::linker::LinkMode;
use pep508_rs::{MarkerEnvironment, Requirement};
Expand Down Expand Up @@ -39,7 +41,7 @@ use crate::commands::{compile_bytecode, elapsed, ChangeEvent, ChangeEventKind, E
use crate::printer::Printer;
use crate::requirements::{ExtrasSpecification, RequirementsSource, RequirementsSpecification};

use super::Upgrade;
use super::{DryRunEvent, Upgrade};

/// Install packages into the current environment.
#[allow(clippy::too_many_arguments, clippy::fn_params_excessive_bools)]
Expand Down Expand Up @@ -69,6 +71,7 @@ pub(crate) async fn pip_install(
break_system_packages: bool,
native_tls: bool,
cache: Cache,
dry_run: bool,
printer: Printer,
) -> Result<ExitStatus> {
let start = std::time::Instant::now();
Expand Down Expand Up @@ -164,6 +167,9 @@ pub(crate) async fn pip_install(
)
.dimmed()
)?;
if dry_run {
writeln!(printer.stderr(), "Would make no changes")?;
}
return Ok(ExitStatus::Success);
}

Expand Down Expand Up @@ -320,6 +326,7 @@ pub(crate) async fn pip_install(
&install_dispatch,
&cache,
&venv,
dry_run,
printer,
)
.await?;
Expand Down Expand Up @@ -392,7 +399,7 @@ async fn build_editables(
build_dispatch: &BuildDispatch<'_>,
printer: Printer,
) -> Result<Vec<BuiltEditable>, Error> {
let start = std::time::Instant::now();
let start = Instant::now();

let downloader = Downloader::new(cache, tags, client, build_dispatch)
.with_reporter(DownloadReporter::from(printer).with_length(editables.len() as u64));
Expand Down Expand Up @@ -558,6 +565,7 @@ async fn install(
build_dispatch: &BuildDispatch<'_>,
cache: &Cache,
venv: &PythonEnvironment,
dry_run: bool,
printer: Printer,
) -> Result<(), Error> {
let start = std::time::Instant::now();
Expand All @@ -572,12 +580,7 @@ async fn install(

// 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`).
let Plan {
local,
remote,
reinstalls,
extraneous: _,
} = Planner::with_requirements(&requirements)
let plan = Planner::with_requirements(&requirements)
.with_editable_requirements(&editables)
.build(
site_packages,
Expand All @@ -590,6 +593,17 @@ async fn install(
)
.context("Failed to determine installation plan")?;

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

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

// Nothing to do.
if remote.is_empty() && local.is_empty() && reinstalls.is_empty() {
let s = if resolution.len() == 1 { "" } else { "s" };
Expand All @@ -603,7 +617,6 @@ async fn install(
)
.dimmed()
)?;

return Ok(());
}

Expand All @@ -622,7 +635,7 @@ async fn install(
let wheels = if remote.is_empty() {
vec![]
} else {
let start = std::time::Instant::now();
let start = Instant::now();

let downloader = Downloader::new(cache, tags, client, build_dispatch)
.with_reporter(DownloadReporter::from(printer).with_length(remote.len() as u64));
Expand Down Expand Up @@ -728,6 +741,135 @@ async fn install(
}
}

#[allow(clippy::items_after_statements)]
fn report_dry_run(
resolution: &Resolution,
plan: Plan,
start: Instant,
printer: Printer,
) -> Result<(), Error> {
let Plan {
local,
remote,
reinstalls,
extraneous: _,
} = plan;

// Nothing to do.
if remote.is_empty() && local.is_empty() && reinstalls.is_empty() {
let s = if resolution.len() == 1 { "" } else { "s" };
writeln!(
printer.stderr(),
"{}",
format!(
"Audited {} in {}",
format!("{} package{}", resolution.len(), s).bold(),
elapsed(start.elapsed())
)
.dimmed()
)?;
writeln!(printer.stderr(), "Would make no changes")?;
return Ok(());
}

// Map any registry-based requirements back to those returned by the resolver.
let remote = remote
.iter()
.map(|dist| {
resolution
.get(&dist.name)
.cloned()
.expect("Resolution should contain all packages")
})
.collect::<Vec<_>>();

// Download, build, and unzip any missing distributions.
let wheels = if remote.is_empty() {
vec![]
} else {
let s = if remote.len() == 1 { "" } else { "s" };
writeln!(
printer.stderr(),
"{}",
format!(
"Would download {}",
format!("{} package{}", remote.len(), s).bold(),
)
.dimmed()
)?;
remote
};

// Remove any existing installations.
if !reinstalls.is_empty() {
let s = if reinstalls.len() == 1 { "" } else { "s" };
writeln!(
printer.stderr(),
"{}",
format!(
"Would uninstall {}",
format!("{} package{}", reinstalls.len(), s).bold(),
)
.dimmed()
)?;
}

// Install the resolved distributions.
let installs = wheels.len() + local.len();

if installs > 0 {
let s = if installs == 1 { "" } else { "s" };
writeln!(
printer.stderr(),
"{}",
format!("Would install {}", format!("{installs} package{s}").bold()).dimmed()
)?;
}

for event in reinstalls
.into_iter()
.map(|distribution| DryRunEvent {
name: distribution.name().clone(),
version: distribution.installed_version().to_string(),
kind: ChangeEventKind::Removed,
})
.chain(wheels.into_iter().map(|distribution| DryRunEvent {
name: distribution.name().clone(),
version: distribution.version_or_url().to_string(),
kind: ChangeEventKind::Added,
}))
.chain(local.into_iter().map(|distribution| DryRunEvent {
name: distribution.name().clone(),
version: distribution.installed_version().to_string(),
kind: ChangeEventKind::Added,
}))
.sorted_unstable_by(|a, b| a.name.cmp(&b.name).then_with(|| a.kind.cmp(&b.kind)))
{
match event.kind {
ChangeEventKind::Added => {
writeln!(
printer.stderr(),
" {} {}{}",
"+".green(),
event.name.as_ref().bold(),
event.version.dimmed()
)?;
}
ChangeEventKind::Removed => {
writeln!(
printer.stderr(),
" {} {}{}",
"-".red(),
event.name.as_ref().bold(),
event.version.dimmed()
)?;
}
}
}

Ok(())
}

// TODO(konstin): Also check the cache whether any cached or installed dist is already known to
// have been yanked, we currently don't show this message on the second run anymore
for dist in &remote {
Expand Down
6 changes: 6 additions & 0 deletions crates/uv/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -891,6 +891,11 @@ struct PipInstallArgs {
/// format (e.g., `2006-12-02`).
#[arg(long, value_parser = date_or_datetime)]
exclude_newer: Option<DateTime<Utc>>,

/// Perform a dry run, i.e., don't actually install anything but resolve the dependencies and
/// print the resulting plan.
#[clap(long)]
dry_run: bool,
}

#[derive(Args)]
Expand Down Expand Up @@ -1586,6 +1591,7 @@ async fn run() -> Result<ExitStatus> {
args.break_system_packages,
cli.native_tls,
cache,
args.dry_run,
printer,
)
.await
Expand Down
Loading