Skip to content
Draft
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
2 changes: 1 addition & 1 deletion crates/uv/src/commands/project/remove.rs
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ pub(crate) async fn remove(
)
.await
{
Ok(()) => {}
Ok(_) => {}
Err(ProjectError::Operation(err)) => {
return diagnostics::OperationDiagnostic::native_tls(client_builder.is_native_tls())
.report(err)
Expand Down
4 changes: 2 additions & 2 deletions crates/uv/src/commands/project/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
)
.await
{
Ok(()) => {}
Ok(_) => {}
Err(ProjectError::Operation(err)) => {
return diagnostics::OperationDiagnostic::native_tls(
client_builder.is_native_tls(),
Expand Down Expand Up @@ -859,7 +859,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
)
.await
{
Ok(()) => {}
Ok(_) => {}
Err(ProjectError::Operation(err)) => {
return diagnostics::OperationDiagnostic::native_tls(
client_builder.is_native_tls(),
Expand Down
138 changes: 112 additions & 26 deletions crates/uv/src/commands/project/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ use uv_configuration::{
use uv_dispatch::BuildDispatch;
use uv_distribution::LoweredExtraBuildDependencies;
use uv_distribution_types::{
DirectorySourceDist, Dist, Index, Requirement, Resolution, ResolvedDist, SourceDist,
DirectorySourceDist, Dist, Index, InstalledMetadata, LocalDist, Name, Requirement, Resolution,
ResolvedDist, SourceDist,
};
use uv_fs::{PortablePathBuf, Simplified};
use uv_installer::{InstallationStrategy, SitePackages};
Expand All @@ -37,16 +38,16 @@ use uv_workspace::pyproject::Source;
use uv_workspace::{DiscoveryOptions, MemberDiscovery, VirtualProject, Workspace, WorkspaceCache};

use crate::commands::pip::loggers::{DefaultInstallLogger, DefaultResolveLogger, InstallLogger};
use crate::commands::pip::operations::Modifications;
use crate::commands::pip::operations::{Changelog, Modifications};
use crate::commands::pip::resolution_markers;
use crate::commands::pip::{operations, resolution_tags};
use crate::commands::project::install_target::InstallTarget;
use crate::commands::project::lock::{LockMode, LockOperation, LockResult};
use crate::commands::project::lock_target::LockTarget;
use crate::commands::project::{
PlatformState, ProjectEnvironment, ProjectError, ScriptEnvironment, UniversalState,
default_dependency_groups, detect_conflicts, script_extra_build_requires, script_specification,
update_environment,
EnvironmentUpdate, PlatformState, ProjectEnvironment, ProjectError, ScriptEnvironment,
UniversalState, default_dependency_groups, detect_conflicts, script_extra_build_requires,
script_specification, update_environment,
};
use crate::commands::{ExitStatus, diagnostics};
use crate::printer::Printer;
Expand Down Expand Up @@ -212,6 +213,7 @@ pub(crate) async fn sync(
environment: EnvironmentReport::from(&environment),
action: SyncAction::from(&environment),
target: TargetName::from(&target),
packages: Vec::new(),
};

// Show the intermediate results if relevant
Expand Down Expand Up @@ -283,14 +285,17 @@ pub(crate) async fn sync(
)
.await
{
Ok(..) => {
Ok(EnvironmentUpdate { changelog, .. }) => {
// Generate a report for the script without a lockfile
let report = Report {
schema: SchemaReport::default(),
target: TargetName::from(&target),
project: None,
script: Some(ScriptReport::from(script)),
sync: sync_report,
sync: SyncReport {
packages: PackageChangeReport::from_changelog(&changelog),
..sync_report
},
lock: None,
dry_run: dry_run.enabled(),
};
Expand Down Expand Up @@ -378,27 +383,13 @@ pub(crate) async fn sync(
writeln!(printer.stderr(), "{message}")?;
}

let report = Report {
schema: SchemaReport::default(),
target: TargetName::from(&target),
project: target.project().map(ProjectReport::from),
script: target.script().map(ScriptReport::from),
sync: sync_report,
lock: Some(lock_report),
dry_run: dry_run.enabled(),
};

if let Some(output) = report.format(output_format) {
writeln!(printer.stdout_important(), "{output}")?;
}

// Identify the installation target.
let sync_target = identify_installation_target(&target, outcome.lock(), all_packages, &package);

let state = state.fork();

// Perform the sync operation.
match do_sync(
let changelog = match do_sync(
sync_target,
&environment,
&extras,
Expand All @@ -421,13 +412,30 @@ pub(crate) async fn sync(
)
.await
{
Ok(()) => {}
Ok(changelog) => changelog,
Err(ProjectError::Operation(err)) => {
return diagnostics::OperationDiagnostic::native_tls(client_builder.is_native_tls())
.report(err)
.map_or(Ok(ExitStatus::Failure), |err| Err(err.into()));
}
Err(err) => return Err(err.into()),
};

let report = Report {
schema: SchemaReport::default(),
target: TargetName::from(&target),
project: target.project().map(ProjectReport::from),
script: target.script().map(ScriptReport::from),
sync: SyncReport {
packages: PackageChangeReport::from_changelog(&changelog),
..sync_report
},
lock: Some(lock_report),
dry_run: dry_run.enabled(),
};

if let Some(output) = report.format(output_format) {
writeln!(printer.stdout_important(), "{output}")?;
}

match outcome {
Expand Down Expand Up @@ -605,7 +613,7 @@ pub(super) async fn do_sync(
dry_run: DryRun,
printer: Printer,
preview: Preview,
) -> Result<(), ProjectError> {
) -> Result<Changelog, ProjectError> {
// Extract the project settings.
let InstallerSettingsRef {
index_locations,
Expand Down Expand Up @@ -811,7 +819,7 @@ pub(super) async fn do_sync(
let site_packages = SitePackages::from_environment(venv)?;

// Sync the environment.
operations::install(
let changelog = operations::install(
&resolution,
site_packages,
InstallationStrategy::Strict,
Expand All @@ -836,7 +844,7 @@ pub(super) async fn do_sync(
)
.await?;

Ok(())
Ok(changelog)
}

/// Filter out any virtual workspace members.
Expand Down Expand Up @@ -1240,6 +1248,9 @@ struct SyncReport {
environment: EnvironmentReport,
/// The action performed during the sync, e.g., what was done to the environment.
action: SyncAction,
/// The packages that changed during the sync.
#[serde(default)]
packages: Vec<PackageChangeReport>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After looking at it... I wonder if we should call it changes? I worry packages could be confusing because it doesn't imply it's only the packages that changed

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless we also want to enumerate all the packages that are unchanged here, with action: check... hm.


// We store these fields so the report can format itself self-contained, but the outer
// [`Report`] is intended to include these in user-facing output
Expand All @@ -1262,6 +1273,7 @@ impl SyncReport {
let Self {
environment,
action,
packages: _,
dry_run,
target,
} = self;
Expand All @@ -1280,6 +1292,80 @@ impl SyncReport {
}
}

/// A summary of a single package change performed during sync.
#[derive(Serialize, Debug, Clone)]
struct PackageChangeReport {
/// The normalized package name.
name: String,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PackageName supports serialization, no? We shouldn't resolve to strings here unless we need to.

/// The resolved version of the package.
version: String,
/// The source for URL-based requirements.
#[serde(skip_serializing_if = "Option::is_none")]
source: Option<PackageChangeSourceReport>,
/// The action that was taken for the package.
action: PackageChangeAction,
}

impl PackageChangeReport {
fn from_changelog(changelog: &Changelog) -> Vec<Self> {
let mut changes: Vec<_> = changelog
.uninstalled
.iter()
.map(|dist| Self::from_dist(dist, PackageChangeAction::Removed))
.chain(
changelog
.installed
.iter()
.map(|dist| Self::from_dist(dist, PackageChangeAction::Added)),
)
.chain(
changelog
.reinstalled
.iter()
.map(|dist| Self::from_dist(dist, PackageChangeAction::Reinstalled)),
)
.collect();

changes.sort_by(|a, b| {
a.name
.cmp(&b.name)
.then_with(|| a.action.cmp(&b.action))
.then_with(|| a.version.cmp(&b.version))
});
changes
}

fn from_dist(dist: &LocalDist, action: PackageChangeAction) -> Self {
let installed_version = dist.installed_version();

Self {
name: dist.name().to_string(),
version: installed_version.version().to_string(),
source: installed_version
.url()
.map(|url| PackageChangeSourceReport {
url: url.to_string(),
}),
action,
}
}
}

/// The action taken on an individual package during sync.
#[derive(Serialize, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[serde(rename_all = "snake_case")]
enum PackageChangeAction {
Removed,
Added,
Reinstalled,
}

/// The source for a package change, when it originated from a URL requirement.
#[derive(Serialize, Debug, Clone)]
struct PackageChangeSourceReport {
url: String,
}

/// The report for a lock operation.
#[derive(Debug, Serialize)]
struct LockReport {
Expand Down
2 changes: 1 addition & 1 deletion crates/uv/src/commands/project/version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -668,7 +668,7 @@ async fn lock_and_sync(
)
.await
{
Ok(()) => {}
Ok(_) => {}
Err(ProjectError::Operation(err)) => {
return diagnostics::OperationDiagnostic::native_tls(client_builder.is_native_tls())
.report(err)
Expand Down
Loading
Loading