Skip to content

Commit

Permalink
Add uv tree --outdated
Browse files Browse the repository at this point in the history
  • Loading branch information
charliermarsh committed Nov 7, 2024
1 parent 107ab3d commit 7c88abd
Show file tree
Hide file tree
Showing 14 changed files with 328 additions and 118 deletions.
4 changes: 4 additions & 0 deletions crates/uv-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4802,6 +4802,10 @@ pub struct DisplayTreeArgs {
/// display the packages that depend on the given package.
#[arg(long, alias = "reverse")]
pub invert: bool,

/// Show the latest available version of each package in the tree.
#[arg(long)]
pub outdated: bool,
}

#[derive(Args, Debug)]
Expand Down
7 changes: 7 additions & 0 deletions crates/uv-distribution-filename/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ impl DistFilename {
}
}

pub fn into_version(self) -> Version {
match self {
Self::SourceDistFilename(filename) => filename.version,
Self::WheelFilename(filename) => filename.version,
}
}

/// Whether the file is a `bdist_wheel` or an `sdist`.
pub fn filetype(&self) -> &'static str {
match self {
Expand Down
4 changes: 2 additions & 2 deletions crates/uv-resolver/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ pub use exclude_newer::ExcludeNewer;
pub use exclusions::Exclusions;
pub use flat_index::{FlatDistributions, FlatIndex};
pub use lock::{
InstallTarget, Lock, LockError, LockVersion, RequirementsTxtExport, ResolverManifest,
SatisfiesResult, TreeDisplay, VERSION,
InstallTarget, Lock, LockError, LockVersion, PackageMap, RequirementsTxtExport,
ResolverManifest, SatisfiesResult, TreeDisplay, VERSION,
};
pub use manifest::Manifest;
pub use options::{Flexibility, Options, OptionsBuilder};
Expand Down
37 changes: 37 additions & 0 deletions crates/uv-resolver/src/lock/map.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use rustc_hash::FxHashMap;

use crate::lock::{Package, PackageId};

/// A map from package to values, indexed by [`PackageId`].
#[derive(Debug, Clone)]
pub struct PackageMap<T>(FxHashMap<PackageId, T>);

impl<T> Default for PackageMap<T> {
fn default() -> Self {
Self(FxHashMap::default())
}
}

impl<T> PackageMap<T> {
/// Get a value by [`PackageId`].
pub(crate) fn get(&self, package_id: &PackageId) -> Option<&T> {
self.0.get(package_id)
}
}

impl<T> FromIterator<(Package, T)> for PackageMap<T> {
fn from_iter<I: IntoIterator<Item = (Package, T)>>(iter: I) -> Self {
Self(
iter.into_iter()
.map(|(package, value)| (package.id, value))
.collect(),
)
}
}

impl<T> Extend<(Package, T)> for PackageMap<T> {
fn extend<I: IntoIterator<Item = (Package, T)>>(&mut self, iter: I) {
self.0
.extend(iter.into_iter().map(|(package, value)| (package.id, value)));
}
}
20 changes: 20 additions & 0 deletions crates/uv-resolver/src/lock/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use std::sync::{Arc, LazyLock};
use toml_edit::{value, Array, ArrayOfTables, InlineTable, Item, Table, Value};
use url::Url;

pub use crate::lock::map::PackageMap;
pub use crate::lock::requirements_txt::RequirementsTxtExport;
pub use crate::lock::target::InstallTarget;
pub use crate::lock::tree::TreeDisplay;
Expand Down Expand Up @@ -47,6 +48,7 @@ use uv_types::{BuildContext, HashStrategy};
use uv_workspace::dependency_groups::DependencyGroupError;
use uv_workspace::Workspace;

mod map;
mod requirements_txt;
mod target;
mod tree;
Expand Down Expand Up @@ -2206,6 +2208,24 @@ impl Package {
self.fork_markers.as_slice()
}

/// Returns the [`IndexUrl`] for the package, if it is a registry source.
pub fn index(&self, root: &Path) -> Result<Option<IndexUrl>, LockError> {
match &self.id.source {
Source::Registry(RegistrySource::Url(url)) => {
let index = IndexUrl::from(VerbatimUrl::from_url(url.to_url()));
Ok(Some(index))
}
Source::Registry(RegistrySource::Path(path)) => {
let index = IndexUrl::from(
VerbatimUrl::from_absolute_path(root.join(path))
.map_err(LockErrorKind::RegistryVerbatimUrl)?,
);
Ok(Some(index))
}
_ => Ok(None),
}
}

/// Returns all the hashes associated with this [`Package`].
fn hashes(&self) -> Vec<HashDigest> {
let mut hashes = Vec::new();
Expand Down
15 changes: 14 additions & 1 deletion crates/uv-resolver/src/lock/tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,28 @@ use std::borrow::Cow;
use std::collections::VecDeque;

use itertools::Itertools;
use owo_colors::OwoColorize;
use petgraph::graph::{EdgeIndex, NodeIndex};
use petgraph::prelude::EdgeRef;
use petgraph::Direction;
use rustc_hash::{FxHashMap, FxHashSet};

use uv_configuration::DevGroupsManifest;
use uv_normalize::{ExtraName, GroupName, PackageName};
use uv_pep440::Version;
use uv_pypi_types::ResolverMarkerEnvironment;

use crate::lock::{Dependency, PackageId};
use crate::Lock;
use crate::{Lock, PackageMap};

#[derive(Debug)]
pub struct TreeDisplay<'env> {
/// The constructed dependency graph.
graph: petgraph::graph::Graph<&'env PackageId, Edge<'env>, petgraph::Directed>,
/// The packages considered as roots of the dependency tree.
roots: Vec<NodeIndex>,
/// The latest known version of each package.
latest: &'env PackageMap<Version>,
/// Maximum display depth of the dependency tree.
depth: usize,
/// Whether to de-duplicate the displayed dependencies.
Expand All @@ -31,6 +35,7 @@ impl<'env> TreeDisplay<'env> {
pub fn new(
lock: &'env Lock,
markers: Option<&'env ResolverMarkerEnvironment>,
latest: &'env PackageMap<Version>,
depth: usize,
prune: &[PackageName],
packages: &[PackageName],
Expand Down Expand Up @@ -242,6 +247,7 @@ impl<'env> TreeDisplay<'env> {
Self {
graph,
roots,
latest,
depth,
no_dedupe,
}
Expand Down Expand Up @@ -306,6 +312,13 @@ impl<'env> TreeDisplay<'env> {
}
}

// Incorporate the latest version of the package, if known.
let line = if let Some(version) = self.latest.get(package_id) {
format!("{line} {}", format!("(latest: v{version})").bold().cyan())
} else {
line
};

let mut dependencies = self
.graph
.edges_directed(cursor.node(), Direction::Outgoing)
Expand Down
124 changes: 124 additions & 0 deletions crates/uv/src/commands/pip/latest.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
use uv_client::{RegistryClient, VersionFiles};
use uv_distribution_filename::DistFilename;
use uv_distribution_types::{IndexCapabilities, IndexUrl};
use uv_normalize::PackageName;
use uv_platform_tags::Tags;
use uv_resolver::{ExcludeNewer, PrereleaseMode, RequiresPython};
use uv_warnings::warn_user_once;

/// A client to fetch the latest version of a package from an index.
///
/// The returned distribution is guaranteed to be compatible with the provided tags and Python
/// requirement.
#[derive(Debug)]
pub(crate) struct LatestClient<'env> {
pub(crate) client: &'env RegistryClient,
pub(crate) capabilities: &'env IndexCapabilities,
pub(crate) prerelease: PrereleaseMode,
pub(crate) exclude_newer: Option<ExcludeNewer>,
pub(crate) tags: Option<&'env Tags>,
pub(crate) requires_python: &'env RequiresPython,
}

impl<'env> LatestClient<'env> {
/// Find the latest version of a package from an index.
pub(crate) async fn find_latest(
&self,
package: &PackageName,
index: Option<&IndexUrl>,
) -> anyhow::Result<Option<DistFilename>, uv_client::Error> {
let mut latest: Option<DistFilename> = None;
for (_, archive) in self
.client
.simple(package, index, self.capabilities)
.await?
{
for datum in archive.iter().rev() {
// Find the first compatible distribution.
let files = rkyv::deserialize::<VersionFiles, rkyv::rancor::Error>(&datum.files)
.expect("archived version files always deserializes");

// Determine whether there's a compatible wheel and/or source distribution.
let mut best = None;

for (filename, file) in files.all() {
// Skip distributions uploaded after the cutoff.
if let Some(exclude_newer) = self.exclude_newer {
match file.upload_time_utc_ms.as_ref() {
Some(&upload_time)
if upload_time >= exclude_newer.timestamp_millis() =>
{
continue;
}
None => {
warn_user_once!(
"{} is missing an upload date, but user provided: {exclude_newer}",
file.filename,
);
}
_ => {}
}
}

// Skip pre-release distributions.
if !filename.version().is_stable() {
if !matches!(self.prerelease, PrereleaseMode::Allow) {
continue;
}
}

// Skip distributions that are yanked.
if file.yanked.is_some_and(|yanked| yanked.is_yanked()) {
continue;
}

// Skip distributions that are incompatible with the Python requirement.
if file
.requires_python
.as_ref()
.is_some_and(|requires_python| {
!self.requires_python.is_contained_by(requires_python)
})
{
continue;
}

// Skip distributions that are incompatible with the current platform.
if let DistFilename::WheelFilename(filename) = &filename {
if self
.tags
.is_some_and(|tags| !filename.compatibility(tags).is_compatible())
{
continue;
}
}

match filename {
DistFilename::WheelFilename(_) => {
best = Some(filename);
break;
}
DistFilename::SourceDistFilename(_) => {
if best.is_none() {
best = Some(filename);
}
}
}
}

match (latest.as_ref(), best) {
(Some(current), Some(best)) => {
if best.version() > current.version() {
latest = Some(best);
}
}
(None, Some(best)) => {
latest = Some(best);
}
_ => {}
}
}
}
Ok(latest)
}
}
Loading

0 comments on commit 7c88abd

Please sign in to comment.