From 06eb8f6293ecd858308f6c1b98364688056896f6 Mon Sep 17 00:00:00 2001 From: Ruben Arts Date: Wed, 17 Jan 2024 11:21:16 +0100 Subject: [PATCH] feat: add environments to the info command output --- src/cli/info.rs | 262 ++++++++++++++++++++++++-------- src/project/manifest/feature.rs | 27 ++++ src/project/mod.rs | 13 ++ 3 files changed, 241 insertions(+), 61 deletions(-) diff --git a/src/cli/info.rs b/src/cli/info.rs index cdc31e9db..8f65db87f 100644 --- a/src/cli/info.rs +++ b/src/cli/info.rs @@ -12,8 +12,9 @@ use tokio::task::spawn_blocking; use crate::progress::await_in_progress; use crate::Project; +static WIDTH: usize = 18; -/// Information about the system and project +/// Information about the system, project and environments for the current machine. #[derive(Parser, Debug)] pub struct Args { /// Show cache and environment size @@ -31,12 +32,127 @@ pub struct Args { #[derive(Serialize)] pub struct ProjectInfo { - tasks: Vec, manifest_path: PathBuf, - package_count: u64, - environment_size: Option, last_updated: Option, + pixi_folder_size: Option, + version: Option, +} + +#[derive(Serialize)] +pub struct EnvironmentInfo { + name: String, + features: Vec, + solve_group: Option, + environment_size: Option, + dependencies: Vec, + pypi_dependencies: Vec, platforms: Vec, + tasks: Vec, + channels: Vec, + // prefix: Option, add when PR 674 is merged +} + +impl Display for EnvironmentInfo { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let bold = console::Style::new().bold(); + writeln!( + f, + "{}", + console::style(self.name.clone()).bold().blue().underlined() + )?; + writeln!( + f, + "{:>WIDTH$}: {}", + bold.apply_to("Features"), + self.features.join(", ") + )?; + if let Some(solve_group) = &self.solve_group { + writeln!( + f, + "{:>WIDTH$}: {}", + bold.apply_to("Solve group"), + solve_group + )?; + } + // TODO: add environment size when PR 674 is merged + if let Some(size) = &self.environment_size { + writeln!(f, "{:>WIDTH$}: {}", bold.apply_to("Environment size"), size)?; + } + if !self.channels.is_empty() { + let channels_list = self + .channels + .iter() + .map(|c| c.to_string()) + .collect::>() + .join(", "); + writeln!( + f, + "{:>WIDTH$}: {}", + bold.apply_to("Channels"), + channels_list + )?; + } + writeln!( + f, + "{:>WIDTH$}: {}", + bold.apply_to("Dependency count"), + self.dependencies.len() + )?; + if !self.dependencies.is_empty() { + let dependencies_list = self + .dependencies + .iter() + .map(|d| d.to_string()) + .collect::>() + .join(", "); + writeln!( + f, + "{:>WIDTH$}: {}", + bold.apply_to("Dependencies"), + dependencies_list + )?; + } + + if !self.pypi_dependencies.is_empty() { + let dependencies_list = self + .pypi_dependencies + .iter() + .map(|d| d.to_string()) + .collect::>() + .join(", "); + writeln!( + f, + "{:>WIDTH$}: {}", + bold.apply_to("PyPI Dependencies"), + dependencies_list + )?; + } + + if !self.platforms.is_empty() { + let platform_list = self + .platforms + .iter() + .map(|p| p.to_string()) + .collect::>() + .join(", "); + writeln!( + f, + "{:>WIDTH$}: {}", + bold.apply_to("Target platforms"), + platform_list + )?; + } + if !self.tasks.is_empty() { + let tasks_list = self + .tasks + .iter() + .map(|t| t.to_string()) + .collect::>() + .join(", "); + writeln!(f, "{:>WIDTH$}: {}", bold.apply_to("Tasks"), tasks_list)?; + } + Ok(()) + } } #[serde_as] @@ -50,77 +166,77 @@ pub struct Info { cache_size: Option, auth_dir: PathBuf, project_info: Option, + environments_info: Vec, } - impl Display for Info { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let blue = console::Style::new().blue(); + let bold = console::Style::new().bold(); let cache_dir = match &self.cache_dir { Some(path) => path.to_string_lossy().to_string(), None => "None".to_string(), }; - writeln!(f, "pixi {}\n", self.version)?; - writeln!(f, "{:20}: {}", "Platform", self.platform)?; + writeln!( + f, + "{:>WIDTH$}: {}", + bold.apply_to("Pixi version"), + blue.apply_to(&self.version) + )?; + writeln!( + f, + "{:>WIDTH$}: {}", + bold.apply_to("Platform"), + self.platform + )?; for (i, p) in self.virtual_packages.iter().enumerate() { if i == 0 { - writeln!(f, "{:20}: {}", "Virtual packages", p)?; + writeln!(f, "{:>WIDTH$}: {}", bold.apply_to("Virtual packages"), p)?; } else { - writeln!(f, "{:20}: {}", "", p)?; + writeln!(f, "{:>WIDTH$}: {}", "", p)?; } } - writeln!(f, "{:20}: {}", "Cache dir", cache_dir)?; + writeln!(f, "{:>WIDTH$}: {}", bold.apply_to("Cache dir"), cache_dir)?; if let Some(cache_size) = &self.cache_size { - writeln!(f, "{:20}: {}", "Cache size", cache_size)?; + writeln!(f, "{:>WIDTH$}: {}", bold.apply_to("Cache size"), cache_size)?; } writeln!( f, - "{:20}: {}", - "Auth storage", + "{:>WIDTH$}: {}", + bold.apply_to("Auth storage"), self.auth_dir.to_string_lossy() )?; if let Some(pi) = self.project_info.as_ref() { - writeln!(f, "\nProject\n------------\n")?; - + writeln!(f, "\n{}", bold.apply_to("Project\n------------"))?; + if let Some(version) = pi.version.clone() { + writeln!(f, "{:>WIDTH$}: {}", bold.apply_to("Version"), version)?; + } writeln!( f, - "{:20}: {}", - "Manifest file", + "{:>WIDTH$}: {}", + bold.apply_to("Manifest file"), pi.manifest_path.to_string_lossy() )?; - writeln!(f, "{:20}: {}", "Dependency count", pi.package_count)?; - - if let Some(size) = &pi.environment_size { - writeln!(f, "{:20}: {}", "Environment size", size)?; - } - if let Some(update_time) = &pi.last_updated { - writeln!(f, "{:20}: {}", "Last updated", update_time)?; - } - - if !pi.platforms.is_empty() { - for (i, p) in pi.platforms.iter().enumerate() { - if i == 0 { - writeln!(f, "{:20}: {}", "Target platforms", p)?; - } else { - writeln!(f, "{:20}: {}", "", p)?; - } - } + writeln!( + f, + "{:>WIDTH$}: {}", + bold.apply_to("Last updated"), + update_time + )?; } + } - if !pi.tasks.is_empty() { - for (i, t) in pi.tasks.iter().enumerate() { - if i == 0 { - writeln!(f, "{:20}: {}", "Tasks", t)?; - } else { - writeln!(f, "{:20}: {}", "", t)?; - } - } + if !self.environments_info.is_empty() { + writeln!(f, "\n{}", bold.apply_to("Environments\n------------"))?; + for e in &self.environments_info { + writeln!(f, "{}", e)?; } } @@ -158,20 +274,12 @@ fn last_updated(path: impl Into) -> miette::Result { Ok(formated_time) } -/// Returns number of dependencies on current platform -fn dependency_count(project: &Project) -> u64 { - project - .dependencies(None, Some(Platform::current())) - .names() - .count() as _ -} - pub async fn execute(args: Args) -> miette::Result<()> { let project = Project::load_or_else_discover(args.manifest_path.as_deref()).ok(); let cache_dir = rattler::default_cache_dir() .map_err(|_| miette::miette!("Could not determine default cache directory"))?; - let (environment_size, cache_size) = if args.extended { + let (pixi_folder_size, cache_size) = if args.extended { let cache_dir = cache_dir.clone(); let env_dir = project.as_ref().map(|p| p.root().join(".pixi")); await_in_progress( @@ -188,20 +296,51 @@ pub async fn execute(args: Args) -> miette::Result<()> { (None, None) }; - let project_info = project.map(|p| ProjectInfo { + let project_info = project.clone().map(|p| ProjectInfo { manifest_path: p.root().to_path_buf().join("pixi.toml"), - tasks: p - .manifest - .tasks(Some(Platform::current())) - .into_keys() - .map(|k| k.to_string()) - .collect(), - package_count: dependency_count(&p), - environment_size, last_updated: last_updated(p.lock_file_path()).ok(), - platforms: p.platforms().into_iter().collect(), + pixi_folder_size, + version: p.version().clone().map(|v| v.to_string()), }); + let environments_info: Vec = project + .as_ref() + .map(|p| { + p.environments() + .iter() + .map(|env| EnvironmentInfo { + name: env.name().as_str().to_string(), + features: env.features().map(|f| f.name.to_string()).collect(), + solve_group: env.manifest().solve_group.clone(), + environment_size: None, + dependencies: env + .dependencies(None, Some(Platform::current())) + .names() + .map(|p| p.as_source().to_string()) + .collect(), + pypi_dependencies: env + .pypi_dependencies(Some(Platform::current())) + .into_iter() + .map(|(name, _p)| name.as_str().to_string()) + .collect(), + platforms: env.platforms().into_iter().collect(), + channels: env + .channels() + .into_iter() + .filter_map(|c| c.name.clone()) + .collect(), + // prefix: env.dir(), + tasks: env + .tasks(Some(Platform::current())) + .expect("Current environment should be supported on current platform") + .into_keys() + .map(|k| k.to_string()) + .collect(), + }) + .collect() + }) + .unwrap_or_default(); + let virtual_packages = VirtualPackage::current() .into_diagnostic()? .iter() @@ -219,6 +358,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { ) .path, project_info, + environments_info, }; if args.json { diff --git a/src/project/manifest/feature.rs b/src/project/manifest/feature.rs index 86c0b648d..1ac9b35a4 100644 --- a/src/project/manifest/feature.rs +++ b/src/project/manifest/feature.rs @@ -1,4 +1,5 @@ use super::{Activation, PyPiRequirement, SystemRequirements, Target, TargetSelector}; +use crate::consts; use crate::project::manifest::channel::{PrioritizedChannel, TomlPrioritizedChannelStrOrMap}; use crate::project::manifest::target::Targets; use crate::project::SpecType; @@ -12,6 +13,7 @@ use serde::{Deserialize, Deserializer}; use serde_with::{serde_as, DisplayFromStr, PickFirst}; use std::borrow::Cow; use std::collections::HashMap; +use std::fmt; /// The name of a feature. This is either a string or default for the default feature. #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)] @@ -44,6 +46,31 @@ impl FeatureName { } } +impl From for String { + fn from(name: FeatureName) -> Self { + match name { + FeatureName::Default => consts::DEFAULT_ENVIRONMENT_NAME.to_string(), + FeatureName::Named(name) => name, + } + } +} +impl<'a> From<&'a FeatureName> for String { + fn from(name: &'a FeatureName) -> Self { + match name { + FeatureName::Default => consts::DEFAULT_ENVIRONMENT_NAME.to_string(), + FeatureName::Named(name) => name.clone(), + } + } +} +impl fmt::Display for FeatureName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + FeatureName::Default => write!(f, "{}", consts::DEFAULT_ENVIRONMENT_NAME), + FeatureName::Named(name) => write!(f, "{}", name), + } + } +} + /// A feature describes a set of functionalities. It allows us to group functionality and its /// dependencies together. /// diff --git a/src/project/mod.rs b/src/project/mod.rs index 8ef022f84..2702986ae 100644 --- a/src/project/mod.rs +++ b/src/project/mod.rs @@ -236,6 +236,19 @@ impl Project { }) } + /// Returns the environments in this project. + pub fn environments(&self) -> Vec { + self.manifest + .parsed + .environments + .iter() + .map(|(_name, env)| Environment { + project: self, + environment: env, + }) + .collect() + } + /// Returns the channels used by this project. /// /// TODO: Remove this function and use the channels from the default environment instead.