mirror of https://github.com/astral-sh/uv
Summarize package changes in `uv sync` json format output (#16981)
## Summary Implement #16653 by making `uv sync --output-format=json` output information about package changes. ## Test Plan Additional tests to test the cases where there is no known package version _may_ be beneficial but as the information used is the same as the information used by the dry run logging now, I don't think that's strictly necessary as those cases are tested. --------- Co-authored-by: Liam <liam@scalzulli.com>
This commit is contained in:
parent
9949f0801f
commit
c2e1983cd6
|
|
@ -122,9 +122,9 @@ pub enum VersionOrUrlRef<'a, T: Pep508Url = VerbatimUrl> {
|
|||
Url(&'a T),
|
||||
}
|
||||
|
||||
impl<T: Pep508Url> VersionOrUrlRef<'_, T> {
|
||||
impl<'a, T: Pep508Url> VersionOrUrlRef<'a, T> {
|
||||
/// If it is a URL, return its value.
|
||||
pub fn url(&self) -> Option<&T> {
|
||||
pub fn url(&self) -> Option<&'a T> {
|
||||
match self {
|
||||
Self::Version(_) => None,
|
||||
Self::Url(url) => Some(url),
|
||||
|
|
|
|||
|
|
@ -454,6 +454,13 @@ impl ChangedDist {
|
|||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn version(&self) -> Option<&Version> {
|
||||
match self {
|
||||
Self::Local(dist) => Some(dist.installed_version().version()),
|
||||
Self::Remote(dist) => dist.version(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A summary of the changes made to the environment during an installation.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
@ -867,7 +867,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(),
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ 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, Name, Requirement, Resolution, ResolvedDist, SourceDist,
|
||||
};
|
||||
use uv_fs::{PortablePathBuf, Simplified};
|
||||
use uv_installer::{InstallationStrategy, SitePackages};
|
||||
|
|
@ -37,16 +37,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::{ChangedDist, 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;
|
||||
|
|
@ -212,6 +212,7 @@ pub(crate) async fn sync(
|
|||
environment: EnvironmentReport::from(&environment),
|
||||
action: SyncAction::from(&environment),
|
||||
target: TargetName::from(&target),
|
||||
changes: PackageChangesReport::default(),
|
||||
};
|
||||
|
||||
// Show the intermediate results if relevant
|
||||
|
|
@ -292,14 +293,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 {
|
||||
changes: PackageChangesReport::from_changelog(&changelog),
|
||||
..sync_report
|
||||
},
|
||||
lock: None,
|
||||
dry_run: dry_run.enabled(),
|
||||
};
|
||||
|
|
@ -387,27 +391,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,
|
||||
|
|
@ -430,13 +420,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 {
|
||||
changes: PackageChangesReport::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 {
|
||||
|
|
@ -614,7 +621,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,
|
||||
|
|
@ -826,7 +833,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,
|
||||
|
|
@ -851,7 +858,7 @@ pub(super) async fn do_sync(
|
|||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
Ok(changelog)
|
||||
}
|
||||
|
||||
/// Filter out any virtual workspace members.
|
||||
|
|
@ -1255,6 +1262,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)]
|
||||
changes: PackageChangesReport,
|
||||
|
||||
// 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
|
||||
|
|
@ -1277,6 +1287,7 @@ impl SyncReport {
|
|||
let Self {
|
||||
environment,
|
||||
action,
|
||||
changes: _,
|
||||
dry_run,
|
||||
target,
|
||||
} = self;
|
||||
|
|
@ -1295,6 +1306,66 @@ impl SyncReport {
|
|||
}
|
||||
}
|
||||
|
||||
/// A summary of all package changes performed during sync.
|
||||
#[derive(Serialize, Debug, Clone, Default)]
|
||||
struct PackageChangesReport(Vec<PackageChangeReport>);
|
||||
|
||||
impl PackageChangesReport {
|
||||
fn from_changelog(changelog: &Changelog) -> Self {
|
||||
let mut changes: Vec<_> =
|
||||
changelog
|
||||
.uninstalled
|
||||
.iter()
|
||||
.map(|dist| PackageChangeReport::from_dist(dist, PackageChangeAction::Uninstalled))
|
||||
.chain(changelog.installed.iter().map(|dist| {
|
||||
PackageChangeReport::from_dist(dist, PackageChangeAction::Installed)
|
||||
}))
|
||||
.chain(changelog.reinstalled.iter().map(|dist| {
|
||||
PackageChangeReport::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))
|
||||
});
|
||||
Self(changes)
|
||||
}
|
||||
}
|
||||
|
||||
/// A summary of a single package change performed during sync.
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
struct PackageChangeReport {
|
||||
/// The normalized package name.
|
||||
name: PackageName,
|
||||
/// The resolved version of the package.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
version: Option<uv_pep440::Version>,
|
||||
/// The action that was taken for the package.
|
||||
action: PackageChangeAction,
|
||||
}
|
||||
|
||||
impl PackageChangeReport {
|
||||
fn from_dist(dist: &ChangedDist, action: PackageChangeAction) -> Self {
|
||||
Self {
|
||||
name: dist.name().clone(),
|
||||
version: dist.version().cloned(),
|
||||
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 {
|
||||
Uninstalled,
|
||||
Installed,
|
||||
Reinstalled,
|
||||
}
|
||||
|
||||
/// The report for a lock operation.
|
||||
#[derive(Debug, Serialize)]
|
||||
struct LockReport {
|
||||
|
|
|
|||
|
|
@ -680,7 +680,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)
|
||||
|
|
|
|||
|
|
@ -420,7 +420,14 @@ fn sync_json() -> Result<()> {
|
|||
"implementation": "cpython"
|
||||
}
|
||||
},
|
||||
"action": "check"
|
||||
"action": "check",
|
||||
"changes": [
|
||||
{
|
||||
"name": "iniconfig",
|
||||
"version": "2.0.0",
|
||||
"action": "installed"
|
||||
}
|
||||
]
|
||||
},
|
||||
"lock": {
|
||||
"path": "[TEMP_DIR]/uv.lock",
|
||||
|
|
@ -464,7 +471,8 @@ fn sync_json() -> Result<()> {
|
|||
"implementation": "cpython"
|
||||
}
|
||||
},
|
||||
"action": "check"
|
||||
"action": "check",
|
||||
"changes": []
|
||||
},
|
||||
"lock": {
|
||||
"path": "[TEMP_DIR]/uv.lock",
|
||||
|
|
@ -503,7 +511,8 @@ fn sync_json() -> Result<()> {
|
|||
"implementation": "cpython"
|
||||
}
|
||||
},
|
||||
"action": "check"
|
||||
"action": "check",
|
||||
"changes": []
|
||||
},
|
||||
"lock": {
|
||||
"path": "[TEMP_DIR]/uv.lock",
|
||||
|
|
@ -569,7 +578,8 @@ fn sync_json() -> Result<()> {
|
|||
"implementation": "cpython"
|
||||
}
|
||||
},
|
||||
"action": "check"
|
||||
"action": "check",
|
||||
"changes": []
|
||||
},
|
||||
"lock": {
|
||||
"path": "[TEMP_DIR]/uv.lock",
|
||||
|
|
@ -629,7 +639,14 @@ fn sync_dry_json() -> Result<()> {
|
|||
"implementation": "cpython"
|
||||
}
|
||||
},
|
||||
"action": "create"
|
||||
"action": "create",
|
||||
"changes": [
|
||||
{
|
||||
"name": "iniconfig",
|
||||
"version": "2.0.0",
|
||||
"action": "installed"
|
||||
}
|
||||
]
|
||||
},
|
||||
"lock": {
|
||||
"path": "[TEMP_DIR]/uv.lock",
|
||||
|
|
@ -6856,7 +6873,24 @@ fn sync_active_script_environment_json() -> Result<()> {
|
|||
"implementation": "cpython"
|
||||
}
|
||||
},
|
||||
"action": "create"
|
||||
"action": "create",
|
||||
"changes": [
|
||||
{
|
||||
"name": "anyio",
|
||||
"version": "4.3.0",
|
||||
"action": "installed"
|
||||
},
|
||||
{
|
||||
"name": "idna",
|
||||
"version": "3.6",
|
||||
"action": "installed"
|
||||
},
|
||||
{
|
||||
"name": "sniffio",
|
||||
"version": "1.3.1",
|
||||
"action": "installed"
|
||||
}
|
||||
]
|
||||
},
|
||||
"lock": null,
|
||||
"dry_run": false
|
||||
|
|
@ -6902,7 +6936,24 @@ fn sync_active_script_environment_json() -> Result<()> {
|
|||
"implementation": "cpython"
|
||||
}
|
||||
},
|
||||
"action": "create"
|
||||
"action": "create",
|
||||
"changes": [
|
||||
{
|
||||
"name": "anyio",
|
||||
"version": "4.3.0",
|
||||
"action": "installed"
|
||||
},
|
||||
{
|
||||
"name": "idna",
|
||||
"version": "3.6",
|
||||
"action": "installed"
|
||||
},
|
||||
{
|
||||
"name": "sniffio",
|
||||
"version": "1.3.1",
|
||||
"action": "installed"
|
||||
}
|
||||
]
|
||||
},
|
||||
"lock": null,
|
||||
"dry_run": false
|
||||
|
|
@ -6961,7 +7012,24 @@ fn sync_active_script_environment_json() -> Result<()> {
|
|||
"implementation": "cpython"
|
||||
}
|
||||
},
|
||||
"action": "update"
|
||||
"action": "update",
|
||||
"changes": [
|
||||
{
|
||||
"name": "anyio",
|
||||
"version": "4.3.0",
|
||||
"action": "installed"
|
||||
},
|
||||
{
|
||||
"name": "idna",
|
||||
"version": "3.6",
|
||||
"action": "installed"
|
||||
},
|
||||
{
|
||||
"name": "sniffio",
|
||||
"version": "1.3.1",
|
||||
"action": "installed"
|
||||
}
|
||||
]
|
||||
},
|
||||
"lock": null,
|
||||
"dry_run": false
|
||||
|
|
|
|||
Loading…
Reference in New Issue